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 d52480a73d90..38d78cf4ea61 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -30,7 +30,6 @@ "new-cap": [ "error", { - "capIsNewExceptionPattern": "^BigInt", "properties": false } ], @@ -95,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": 2018, + "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 0483107ca2ef..3c8f2947202c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +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* -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/.npmrc b/.npmrc new file mode 100644 index 000000000000..43c97e719a5a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5f8d7be2ff5e..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,90 +0,0 @@ -sudo: true -dist: trusty - -language: node_js - -branches: - only: - - master - - /^greenkeeper/.*$/ - except: - - /^v\d+\.\d+\.\d+$/ - -cache: npm - -install: - - 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: '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 lint-docs #change after v6 released - 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 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 f922bd1929c6..b7b25a556cf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,162 +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 + +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`. -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. +### 2. Install the Node.js dependencies -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`. +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 ``` -> **_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. +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: -### 4. Running tests +- `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`). -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 dcdee4b0fe68..3878c9b9e336 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,61 @@ # 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) -[![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize?icon=codecov)](https://codecov.io/gh/sequelize/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 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](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-beta 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. + +### v6 Release + +You can find the detailed changelog [here](https://github.com/sequelize/sequelize/blob/main/docs/manual/other-topics/upgrade-to-v6.md). -[![npm version](https://badgen.net/npm/v/sequelize/next)](https://www.npmjs.com/package/sequelize) +## Supporting the project -`v6-beta` is now available. You can find detailed changelog [here](https://github.com/sequelize/sequelize/blob/master/docs/manual/upgrade-to-v6.md). +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 v5 -$ npm install --save sequelize@next # This will install v6-beta +```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-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) +- [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 + - [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 +64,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) (OFFICIAL) + +- [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..5260ac0b1702 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,8 @@ The following table describes the versions of this project that are currently su | Version | Supported | | ------- | ------------------ | -| 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 e65bba3af492..000000000000 --- a/appveyor.yml +++ /dev/null @@ -1,43 +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 - - 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 - - /^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/index.md b/docs/index.md index d1bc0cfe70ec..c49d1a0fc882 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,23 +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 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 +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 cf7a3da64266..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-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-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 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..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 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..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 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/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 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/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-v6.md b/docs/manual/upgrade-to-v6.md deleted file mode 100644 index 1958aa3a209c..000000000000 --- a/docs/manual/upgrade-to-v6.md +++ /dev/null @@ -1,92 +0,0 @@ -# Upgrade to v6 - -Sequelize v6 is the next major release after v5 - -## 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); -``` - -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. - -### Model - -**`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: ['*']` - -**`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. - -```js - const instance = await MyModel.findOne(); - - instance.myJsonField.a = 1; - console.log(instance.changed()); // logs `false` - - await instance.save(); // this will not save anything - - instance.changed('myJsonField', true); - console.log(instance.changed()); // logs `["myJsonField"]` - - await instance.save(); // will save -``` - -## Changelog - -### 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/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/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/lib/associations/base.js b/lib/associations/base.js index a440e3ef84f1..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 = ''; @@ -135,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/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 9fcbe758b627..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,8 +282,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; @@ -335,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(); @@ -355,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 }); @@ -366,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 }); @@ -376,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 }); } @@ -404,14 +405,16 @@ 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 + * @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) { + async get(instance, options) { options = Utils.cloneDeep(options) || {}; const through = this.through; @@ -419,7 +422,7 @@ class BelongsToMany extends Association { let throughWhere; if (this.scope) { - scopeWhere = _.clone(this.scope); + scopeWhere = { ...this.scope }; } options.where = { @@ -449,6 +452,7 @@ class BelongsToMany extends Association { association: this.oneFromTarget, attributes: options.joinTableAttributes, required: true, + paranoid: _.get(options.through, 'paranoid', true), where: throughWhere }); } @@ -473,13 +477,13 @@ 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} */ - count(instance, options) { + async count(instance, options) { const sequelize = this.target.sequelize; options = Utils.cloneDeep(options); @@ -490,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); } /** @@ -498,22 +504,22 @@ 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} */ - 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) { @@ -531,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; } /** @@ -543,29 +549,29 @@ 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} */ - 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 = []; @@ -588,7 +594,7 @@ class BelongsToMany extends Association { throughAttributes = {}; } - const attributes = _.defaults({}, throughAttributes, defaultAttributes); + const attributes = { ...defaultAttributes, ...throughAttributes }; if (Object.keys(attributes).length) { promises.push( @@ -605,44 +611,42 @@ 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 - }, options)) + this.through.model.destroy({ + ...options, + where: { + [identifier]: sourceInstance.get(sourceKey), + [foreignIdentifier]: obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]), + ...this.through.scope + } + }) ); } 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 { + ...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(_.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({ ...options, where, raw: true }); + return await updateAssociations(currentRows); + } catch (error) { + if (error instanceof EmptyResultError) return updateAssociations([]); + throw error; + } } /** @@ -651,17 +655,17 @@ 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} */ - 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 = _.clone(options) || {}; + options = { ...options }; const association = this; const sourceKey = association.sourceKey; @@ -674,11 +678,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 = []; @@ -690,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); @@ -701,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); @@ -711,35 +714,34 @@ 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) { 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 = {}; } - 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); + 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({ ...options, where, raw: true }); + const [associations] = await updateAssociations(currentRows); + return associations; + } catch (error) { + if (error instanceof EmptyResultError) return updateAssociations(); + throw error; + } } /** @@ -747,7 +749,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} */ @@ -763,20 +765,20 @@ 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 }); } /** * 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} */ - create(sourceInstance, values, options) { + async create(sourceInstance, values, options) { const association = this; options = options || {}; @@ -796,9 +798,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'])).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 eafeedb45684..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]; @@ -114,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 * @@ -123,7 +124,7 @@ class BelongsTo extends Association { * * @returns {Promise} */ - get(instances, options) { + async get(instances, options) { const where = {}; let Target = this.target; let instance; @@ -149,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) { @@ -164,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); @@ -186,12 +186,12 @@ 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} */ - set(sourceInstance, associatedInstance, options = {}) { + async set(sourceInstance, associatedInstance, options = {}) { let value = associatedInstance; if (associatedInstance instanceof this.target) { @@ -202,36 +202,37 @@ 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); } /** * 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 * * @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 9350de9eda1f..e8c184bb630b 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -112,14 +112,16 @@ class HasMany extends Association { // the id is in the target table // or in an extra table which connects two tables _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 newAttributes = { + [this.foreignKey]: { + type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, + allowNull: true, + ...this.foreignKeyAttribute + } + }; - newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, { - type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, - allowNull: true - }); + // 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 }; if (this.options.constraints !== false) { const target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey]; @@ -158,8 +160,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 * @@ -168,7 +170,7 @@ class HasMany extends Association { * * @returns {Promise>} */ - get(instances, options = {}) { + async get(instances, options = {}) { const where = {}; let Model = this.target; @@ -180,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 = { @@ -223,33 +225,32 @@ 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; } /** * 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} */ - count(instance, options) { + async count(instance, options) { options = Utils.cloneDeep(options); options.attributes = [ @@ -264,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); } /** @@ -272,22 +275,23 @@ 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} */ - 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) { @@ -305,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; } /** @@ -313,73 +319,76 @@ 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} */ - 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, { ...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, - _.defaults({ - where: updateWhere - }, options) - )); - } + 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, - _.defaults({ - where: updateWhere - }, options) - )); - } + promises.push(this.target.unscoped().update( + update, + { + ...options, + where: updateWhere + } + )); + } - return Utils.Promise.all(promises).return(sourceInstance); - }); + await Promise.all(promises); + + return sourceInstance; } /** @@ -388,19 +397,20 @@ 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} */ - 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 => @@ -408,7 +418,9 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, _.defaults({ where }, options)).return(sourceInstance); + await this.target.unscoped().update(update, { ...options, where }); + + return sourceInstance; } /** @@ -416,11 +428,11 @@ 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} */ - remove(sourceInstance, targetInstances, options = {}) { + async remove(sourceInstance, targetInstances, options = {}) { const update = { [this.foreignKey]: null }; @@ -434,19 +446,21 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, _.defaults({ where }, options)).return(this); + await this.target.unscoped().update(update, { ...options, where }); + + return this; } /** * 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} */ - create(sourceInstance, values, options = {}) { + async create(sourceInstance, values, options = {}) { if (Array.isArray(options)) { options = { fields: options @@ -466,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 b955fb5a39ff..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]; @@ -113,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 * @@ -122,7 +123,7 @@ class HasOne extends Association { * * @returns {Promise} */ - get(instances, options) { + async get(instances, options) { const where = {}; let Target = this.target; @@ -149,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); @@ -164,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); @@ -186,64 +186,60 @@ 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} */ - 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 = 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; } /** * 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 * * @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/lib/associations/helpers.js b/lib/associations/helpers.js index 45c68915fdd7..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; } @@ -46,10 +38,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/associations/mixin.js b/lib/associations/mixin.js index 60601d77d82b..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); @@ -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..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' + ]); } } @@ -157,7 +164,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? @@ -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 Defining a Geometry type attribute * 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) => { @@ -1034,7 +1057,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/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 b37d412e0a2c..8f6ee9f44f99 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -3,9 +3,9 @@ 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'); /** @@ -61,7 +61,7 @@ class ConnectionManager { * @param {string} moduleName Name of dialect module to lookup * * @private - * @returns {Object} + * @returns {object} */ _loadDialectModule(moduleName) { try { @@ -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,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`); @@ -177,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, @@ -204,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, @@ -219,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 }) }; @@ -230,22 +234,20 @@ 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 * * @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 @@ -255,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; } - 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'); }); + 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; } /** @@ -292,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'); } /** @@ -306,10 +313,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).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; } /** @@ -319,10 +327,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); } /** diff --git a/lib/dialects/abstract/index.js b/lib/dialects/abstract/index.js index 618845f7e06f..c9e3c91c9bde 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, @@ -59,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/query-generator.js b/lib/dialects/abstract/query-generator.js old mode 100755 new mode 100644 index 5de520d06b74..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'); @@ -91,9 +90,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 */ @@ -157,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) { @@ -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;`; @@ -243,9 +241,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 */ @@ -278,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' }); @@ -310,17 +309,29 @@ 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, + ';' + ]); } /** * 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 */ @@ -350,7 +361,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; @@ -389,7 +400,7 @@ class QueryGenerator { } } - const whereOptions = _.defaults({ bindParam }, options); + const whereOptions = { ...options, bindParam }; if (values.length === 0) { return ''; @@ -407,20 +418,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 {Object} options - * @param {Object} attributes + * @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 + * + * @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 +443,29 @@ 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)}`); - } - - attributes = attributes || {}; - 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 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 + ]); } /* @@ -481,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) { @@ -503,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})`; } @@ -557,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 ); @@ -566,16 +598,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) { @@ -630,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) { @@ -646,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; } @@ -656,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) + ]); } /* @@ -878,7 +927,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} @@ -1103,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 @@ -1162,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, @@ -1182,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 }); @@ -1292,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}`); } } @@ -1333,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(''); @@ -1400,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}`; } @@ -1482,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) { @@ -1642,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}`; @@ -1675,6 +1732,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), @@ -1689,8 +1752,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 */ @@ -1746,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; @@ -1811,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) { @@ -1877,7 +1933,7 @@ class QueryGenerator { return; } - nestedIncludes = [Object.assign({}, child, { include: nestedIncludes, attributes: [] })]; + nestedIncludes = [{ ...child, include: nestedIncludes, attributes: [] }]; child = parent; } @@ -1954,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 @@ -2015,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) { @@ -2036,7 +2102,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 */ @@ -2094,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); @@ -2126,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(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) { @@ -2198,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 }); } } @@ -2362,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) => { @@ -2526,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 = { 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/operators.js b/lib/dialects/abstract/query-generator/operators.js index 0bff9bad7c74..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: {}, @@ -51,7 +52,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-generator/transaction.js b/lib/dialects/abstract/query-generator/transaction.js index 3d1b0267ec40..c047008f9696 100644 --- a/lib/dialects/abstract/query-generator/transaction.js +++ b/lib/dialects/abstract/query-generator/transaction.js @@ -1,13 +1,13 @@ 'use strict'; -const uuidv4 = require('uuid/v4'); +const uuidv4 = require('uuid').v4; 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-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 __ + * @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 6cd6b8b0289d..b28a93d5e9e6 --- 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(); } @@ -38,10 +39,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) { @@ -83,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; } @@ -206,7 +206,7 @@ class AbstractQuery { } handleShowTablesQuery(results) { - return _.flatten(results.map(resultSet => _.values(resultSet))); + return _.flatten(results.map(resultSet => Object.values(resultSet))); } isShowIndexesQuery() { @@ -324,17 +324,21 @@ 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. */ _logQuery(sql, debugContext, parameters) { const { connection, options } = this; const benchmark = this.sequelize.options.benchmark || options.benchmark; + + const instanceType = connection.queryType === 'read' ? 'replica' : 'primary'; + const instanceTypeMessage = `on ${instanceType} DB`; + const logQueryParameters = this.sequelize.options.logQueryParameters || options.logQueryParameters; const startTime = Date.now(); let logParameter = ''; - + if (logQueryParameters && parameters) { const delimiter = sql.endsWith(';') ? '' : ';'; let paramStr; @@ -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); } }; } @@ -397,8 +402,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..2c163be59005 100644 --- a/lib/dialects/mariadb/connection-manager.js +++ b/lib/dialects/mariadb/connection-manager.js @@ -1,8 +1,8 @@ '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'); @@ -16,11 +16,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; @@ -49,11 +46,11 @@ 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 */ - 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') @@ -69,13 +66,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) { @@ -89,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 3baed56342ae..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,15 +13,20 @@ 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, @@ -48,9 +54,10 @@ MariadbDialect.prototype.supports = _.merge( 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 3422dbeb9a04..2d2048dc59ec 100644 --- a/lib/dialects/mariadb/query-generator.js +++ b/lib/dialects/mariadb/query-generator.js @@ -1,18 +1,23 @@ 'use strict'; const MySQLQueryGenerator = require('../mysql/query-generator'); +const Utils = require('./../../utils'); class MariaDBQueryGenerator extends MySQLQueryGenerator { createSchema(schema, options) { - options = Object.assign({ + options = { charset: null, - 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};`; + collate: null, + ...options + }; + + 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 +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 9960f0d9f7e6..b5de1900d64c 100644 --- a/lib/dialects/mssql/connection-manager.js +++ b/lib/dialects/mssql/connection-manager.js @@ -1,8 +1,7 @@ '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; @@ -26,7 +25,7 @@ class ConnectionManager extends AbstractConnectionManager { parserStore.clear(); } - connect(config) { + async connect(config) { const connectionConfig = { server: config.host, authentication: { @@ -39,7 +38,7 @@ class ConnectionManager extends AbstractConnectionManager { options: { port: parseInt(config.port, 10), database: config.database, - encrypt: false + trustServerCertificate: true } }; @@ -59,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); } @@ -140,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(); @@ -163,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 48adfe9ec68a..4aeb6a495890 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,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 33c7b9bc2914..94e643ee0b23 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)}` : ''; @@ -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,21 +306,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) { @@ -310,8 +336,6 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { const allAttributes = []; const allQueries = []; - - let needIdentityInsertWrapper = false, outputFragment = ''; @@ -441,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; } @@ -499,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) { @@ -623,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 = ''; @@ -701,7 +723,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} */ @@ -718,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', @@ -743,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() { @@ -796,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) { @@ -871,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 631cd19caeb3..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 1f01c7fef185..bbead007705a 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'); @@ -9,6 +8,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,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; @@ -36,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) => { @@ -85,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, errStack); + } + + complete(); + + if (Array.isArray(rows)) { + rows = rows.map(columns => { const row = {}; for (const column of columns) { const typeid = column.metadata.type.id; @@ -96,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) { @@ -140,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, ''); @@ -187,8 +187,8 @@ class Query extends AbstractQuery { result[_result.Name].type += `(${_result.Length})`; } } - } + return result; } if (this.isSelectQuery()) { return this.handleSelectQuery(data); @@ -196,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; @@ -214,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) { @@ -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() { @@ -380,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 8a1414c31b59..1eda51826726 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -2,12 +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 @@ -16,11 +16,8 @@ const parserStore = require('../parserStore')('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; @@ -49,12 +46,12 @@ 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 */ - connect(config) { - const connectionConfig = Object.assign({ + async connect(config) { + const connectionConfig = { host: config.host, port: config.port, user: config.username, @@ -64,85 +61,81 @@ class ConnectionManager extends AbstractConnectionManager { timezone: this.sequelize.options.timezone, 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 4e5cadbda148..a9850b11e83b 100644 --- a/lib/dialects/mysql/index.js +++ b/lib/dialects/mysql/index.js @@ -6,49 +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', - 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 + } +); -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 f6f23f1a0752..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({ + 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() { @@ -65,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 = {}; @@ -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)}`; } @@ -300,14 +314,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 +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 === '(') { @@ -488,20 +502,27 @@ 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 */ 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', + ';' + ]); } /** * 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 @@ -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 48e4aba3bfdd..bcdec9d2e5a7 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -1,91 +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) { - options = options || {}; + /** + * @override + */ + async upsert(tableName, insertValues, updateValues, where, options) { + options = { ...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) - )); -} + options.type = QueryTypes.UPSERT; + options.updateOnDuplicate = Object.keys(updateValues); -/** - * @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); + 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/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 68ead0fa1397..fc9ab0f420a7 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 */ @@ -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 b20d01f25b63..f5d10c949298 --- 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); @@ -243,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; @@ -332,34 +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 = _.defaults({ bindParam: false, returning: ['*'] }, options); - 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)}`, @@ -384,7 +359,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; @@ -600,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; @@ -610,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});`; @@ -627,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'; @@ -639,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 = []; @@ -688,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 => { @@ -835,15 +798,11 @@ 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); } - 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-interface.js b/lib/dialects/postgres/query-interface.js index 0375e5ca7bdf..19a07e467564 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -1,62 +1,55 @@ 'use strict'; const DataTypes = require('../../data-types'); -const Promise = require('../../promise'); const QueryTypes = require('../../query-types'); -const _ = require('lodash'); - +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 {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) => { - const valueOptions = _.clone(options); + const valueOptions = { ...options }; valueOptions.before = null; valueOptions.after = null; @@ -71,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); }); @@ -90,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 @@ -142,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 df7c6e573c8c..9024e5dea6e7 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'); @@ -15,7 +14,7 @@ class Query extends AbstractQuery { * Rewrite query with parameters. * * @param {string} sql - * @param {Array|Object} values + * @param {Array|object} values * @param {string} dialect * @private */ @@ -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,219 +72,237 @@ 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; } + this.sequelize.log(`FAILED QUERY (${connection.uuid || 'default'}): ${this.sql}`, this.options); + 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 => _.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 +321,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 +357,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 +379,8 @@ class Query extends AbstractQuery { constraint: err.constraint, fields, table: err.table, - parent: err + parent: err, + stack: errStack }); case '42704': @@ -358,12 +396,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 +415,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 5c7f11679c9d..eb6017bbd197 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -1,12 +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) { @@ -19,17 +21,16 @@ class ConnectionManager extends AbstractConnectionManager { } this.connections = {}; - this.lib = this._loadDialectModule('sqlite3').verbose(); + this.lib = this._loadDialectModule('sqlite3'); this.refreshTypeParser(dataTypes); } - _onProcessExit() { - const promises = Object.getOwnPropertyNames(this.connections) - .map(connection => Promise.fromCallback(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 @@ -41,39 +42,57 @@ class ConnectionManager extends AbstractConnectionManager { 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; + + 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]; } - return new Promise((resolve, reject) => { + if (!options.inMemory && (options.readWriteMode & this.lib.OPEN_CREATE) !== 0) { + // automatic path provision for `options.storage` + fs.mkdirSync(path.dirname(options.storage), { recursive: true }); + } + + const connection = await 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}`); 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) { 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/index.js b/lib/dialects/sqlite/index.js index 94c8934fe95b..35ecbe9b5e06 100644 --- a/lib/dialects/sqlite/index.js +++ b/lib/dialects/sqlite/index.js @@ -6,46 +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, - '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 + }, + 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 a4f734934ec6..82d65c44c828 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) { @@ -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}`); @@ -175,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 = _.defaults({ bindParam }, options); - 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); @@ -221,7 +210,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)})`; @@ -257,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; @@ -430,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) @@ -473,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 150fa6373fd8..ca2b68653d0f 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -1,208 +1,238 @@ 'use strict'; -const _ = require('lodash'); -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 - */ - -/** - 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 */ -function removeColumn(qi, tableName, attributeName, options) { - options = 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]; - options = 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. + * + * @override + */ + async changeColumn(tableName, attributeName, dataTypeOrOptions, options) { + options = options || {}; - return qi.describeTable(tableName, options).then(fields => { - fields[attributeName] = attributes[attributeName]; + 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; - -/** - 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) { - options = options || {}; - - return qi.describeTable(tableName, options).then(fields => { - fields[attrNameAfter] = _.clone(fields[attrNameBefore]); + 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); + + 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 83e26c73cb32..d0eeecdf1278 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'); @@ -21,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 */ @@ -67,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 @@ -202,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) { @@ -365,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 }); } @@ -413,42 +396,41 @@ 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); + if (err.message && err.message.includes("SQLITE_ERROR: no such table")) { + console.log(err.sql); + } + 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/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..c095754040ae 100644 --- a/lib/errors/bulk-record-error.js +++ b/lib/errors/bulk-record-error.js @@ -4,10 +4,10 @@ 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 + * @param {object} record DAO instance that error belongs to */ class BulkRecordError extends BaseError { constructor(error, record) { @@ -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..4a3a8a38e989 100644 --- a/lib/errors/connection-error.js +++ b/lib/errors/connection-error.js @@ -11,11 +11,11 @@ class ConnectionError extends BaseError { this.name = 'SequelizeConnectionError'; /** * The connection specific error which triggered this one + * * @type {Error} */ 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..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,15 +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; - Error.captureStackTrace(this, this.constructor); + /** + * 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 581b36efbfb4..66ced0e5b9b5 100644 --- a/lib/errors/database/exclusion-constraint-error.js +++ b/lib/errors/database/exclusion-constraint-error.js @@ -10,14 +10,13 @@ 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 || ''; 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..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'; @@ -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..2342c1ab3d14 100644 --- a/lib/errors/database/timeout-error.js +++ b/lib/errors/database/timeout-error.js @@ -6,10 +6,9 @@ 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'; - 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..0c1be5d47a15 100644 --- a/lib/errors/database/unknown-constraint-error.js +++ b/lib/errors/database/unknown-constraint-error.js @@ -10,14 +10,13 @@ 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'; 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/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/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..0c0ff3eb7db4 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; @@ -26,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 40c4fa5386e6..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,7 +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'); } - Error.captureStackTrace(this, this.constructor); + + // Allow overriding the stack if the original stacktrace is uninformative + if (options && options.stack) { + this.stack = options.stack; + } } /** @@ -56,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 * @@ -78,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; @@ -101,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 || []; @@ -180,7 +184,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 +200,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/errors/validation/unique-constraint-error.js b/lib/errors/validation/unique-constraint-error.js index c02f842ea781..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; @@ -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/hooks.js b/lib/hooks.js index 2599bf44f4cd..bbcd4a23aa57 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 = { @@ -49,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; @@ -75,7 +75,7 @@ const Hooks = { /** * Process user supplied hooks definition * - * @param {Object} hooks hooks definition + * @param {object} hooks hooks definition * * @private * @memberof Sequelize @@ -89,7 +89,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 +121,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); - }).return(); + await hook.apply(this, hookArgs); + } }, /** @@ -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..1e4f343a4160 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -3,37 +3,40 @@ 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. * * @param {Instance} modelInstance The model instance. - * @param {Object} options A dictionary with options. + * @param {object} options A dictionary with options. * * @private */ class InstanceValidator { constructor(modelInstance, options) { - options = _.clone(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; /** * Exposes a reference to validator.js. This allows you to add custom validations using `validator.extend` + * * @name validator * @private */ @@ -61,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); + } } /** @@ -86,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()); } /** @@ -100,25 +103,28 @@ 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)) - .return(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; } /** * 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 = []; @@ -139,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); } /** @@ -181,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 @@ -205,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; } @@ -217,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); + }))); } /** @@ -238,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; @@ -257,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); + } } /** @@ -280,23 +290,21 @@ 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(() => { - // 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 }); + } } /** @@ -329,7 +337,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. * @@ -337,7 +345,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`); @@ -367,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. * @@ -421,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 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 99bca1b2b0cd..9960422d2383 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'); @@ -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; } /** @@ -77,18 +76,19 @@ 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` */ 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); @@ -105,11 +105,11 @@ class Model { this.dataValues = {}; this._previousDataValues = {}; this._changed = new Set(); - this._modelOptions = this.constructor.options; 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 && _.clone(values) || {}; + values = { ...values }; if (options.isNewRecord) { defaults = {}; @@ -271,23 +271,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; } @@ -533,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; @@ -543,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; } } @@ -609,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); } @@ -834,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: { @@ -860,12 +852,16 @@ 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 + * @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 +878,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 +910,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} */ @@ -951,7 +947,7 @@ class Model { } delete options.modelName; - this.options = Object.assign({ + this.options = { timestamps: true, validate: {}, freezeTableName: false, @@ -963,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)) { @@ -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); } } @@ -1068,7 +1077,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) => { @@ -1264,134 +1273,122 @@ 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 * * @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.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); + } + + 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])); - } - }); + } - 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(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.runHooks('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; } /** * 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). * * @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); } /** @@ -1405,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). @@ -1437,10 +1434,10 @@ 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); + return this.queryGenerator.addSchema(this); } /** @@ -1458,14 +1455,12 @@ 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) { - 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`); @@ -1519,7 +1514,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. */ @@ -1569,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.`); @@ -1640,48 +1636,50 @@ 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 {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 + * @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. * @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 + * @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'); } @@ -1706,77 +1704,70 @@ class Model { ? options.rejectOnEmpty : this.options.rejectOnEmpty; - return Promise.try(() => { - 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); - }).tap(results => { - if (options.hooks) { - return this.runHooks('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) { @@ -1809,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); @@ -1834,35 +1825,37 @@ 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; } /** * 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) * @@ -1871,10 +1864,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) || {}; @@ -1888,24 +1881,22 @@ 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 {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) * * @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'); } @@ -1924,7 +1915,7 @@ class Model { } // Bypass a possible overloaded findAll. - return this.findAll(_.defaults(options, { + return await this.findAll(_.defaults(options, { plain: true })); } @@ -1934,8 +1925,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,9 +1934,9 @@ 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) { + 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. @@ -1993,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; } /** @@ -2006,9 +1993,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 @@ -2021,45 +2008,53 @@ class Model { * * @returns {Promise} */ - static count(options) { - return Promise.try(() => { - 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}`; - } - - 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,21 +2066,21 @@ 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 * - * @param {Object} [options] See findAll options + * @param {object} [options] See findAll options * * @see * {@link Model.findAll} for a specification of find and query options * @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,66 +2091,67 @@ 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 + }; } /** * 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 * * @returns {Promise<*>} */ - static max(field, options) { - return this.aggregate(field, 'max', options); + static async max(field, options) { + return await this.aggregate(field, 'max', options); } /** * 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 * * @returns {Promise<*>} */ - static min(field, options) { - return this.aggregate(field, 'min', options); + static async min(field, options) { + return await this.aggregate(field, 'min', options); } /** * 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 * * @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 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` @@ -2171,9 +2167,7 @@ class Model { } static bulkBuild(valueSets, options) { - options = Object.assign({ - isNewRecord: true - }, options || {}); + options = { isNewRecord: true, ...options }; if (!options.includeValidated) { this._conformIncludes(options, this); @@ -2198,8 +2192,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` @@ -2216,10 +2210,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, @@ -2232,14 +2226,14 @@ 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} */ - 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. ' + @@ -2249,20 +2243,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 = { ...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]; } /** @@ -2276,14 +2269,14 @@ 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} */ - 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. ' + @@ -2291,7 +2284,7 @@ class Model { ); } - options = Object.assign({}, options); + options = { ...options }; if (options.defaults) { const defaults = Object.keys(options.defaults); @@ -2313,18 +2306,17 @@ 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) || {}; + values = { ...options.defaults }; if (_.isPlainObject(options.where)) { values = Utils.defaults(values, options.where); } @@ -2332,14 +2324,16 @@ 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)); @@ -2363,56 +2357,70 @@ 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 * {@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} */ - static findCreateFind(options) { + static async findCreateFind(options) { if (!options || !options.where) { throw new Error( 'Missing where attribute in the options parameter passed to findCreateFind.' ); } - let values = _.clone(options.defaults) || {}; + let values = { ...options.defaults }; if (_.isPlainObject(options.where)) { values = Utils.defaults(values, options.where); } - 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]; + } } /** @@ -2420,93 +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 + * **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 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 = this.build(values); + options.model = this; + options.instance = instance; + const changed = Array.from(instance._changed); if (!options.fields) { options.fields = changed; } - return Promise.try(() => { - 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 && !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.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; - }) - .tap(result => { - if (options.hooks) { - return this.runHooks('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; } /** @@ -2516,10 +2517,10 @@ 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 + * @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? @@ -2534,13 +2535,14 @@ 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; const now = Utils.now(this.sequelize.options.dialect); + options = Utils.cloneDeep(options); options.model = this; @@ -2554,13 +2556,14 @@ class Model { 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) { @@ -2571,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; @@ -2590,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.runHooks('beforeBulkCreate', instances, options); - } - }).then(() => { - // Validate - if (options.validate) { - const errors = new Promise.AggregateError(); - const validateOptions = _.clone(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 = _.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 = []; + 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 = []; @@ -2659,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 }); } + })); + } + + // 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; - const out = Object.assign({}, Utils.mapValueFieldNames(values, options.fields, model)); - for (const key of model._virtualAttributes) { - delete out[key]; + // 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 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); - // 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[0]).value(); + 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 => _.get(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); + } + + // 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 attr = _.find(model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); + 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]; - instance.dataValues[attr && attr.fieldName || key] = record; - } - } - }); + 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 = []; @@ -2779,79 +2787,80 @@ 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); - } - }); - }); - }).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); - }; - - return recursiveBulkCreate(instances, options); - } + instance.isNewRecord = false; + }); + + // Run after hook + if (options.hooks) { + await model.runHooks('afterBulkCreate', instances, options); + } + + return instances; + }; + + return await recursiveBulkCreate(instances, options); + } /** * 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 @@ -2864,17 +2873,17 @@ 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); } /** * 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 @@ -2888,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); @@ -2914,58 +2923,55 @@ 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.try(() => { - // 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 }) - .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); - }).tap(() => { - // Run afterDestroy hook on each record individually - if (options.individualHooks) { - return Promise.map(instances, instance => this.runHooks('afterDestroy', instance, options)); - } - }).tap(() => { - // Run after hook - if (options.hooks) { - return this.runHooks('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; } /** * 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) @@ -2975,64 +2981,60 @@ 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.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 }) - .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); - }).tap(() => { - // Run afterDestroy hook on each record individually - if (options.individualHooks) { - return Promise.map(instances, instance => this.runHooks('afterRestore', instance, options)); - } - }).tap(() => { - // Run after hook - if (options.hooks) { - return this.runHooks('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 {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 @@ -3050,7 +3052,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); @@ -3091,175 +3093,150 @@ 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 = 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) { + 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; - // 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 + }); + + 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.map(instances, 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.map(instances, instance => { - const individualOptions = _.clone(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.runHooks('afterUpdate', instance, options); - }).then(() => { - result[1] = instances; - }); - } - }).tap(() => { - // Run after hook - if (options.hooks) { - options.attributes = values; - return this.runHooks('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; } /** * 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 */ - 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) { @@ -3296,10 +3273,6 @@ class Model { return this.name; } - static inspect() { - return this.name; - } - static hasAlias(alias) { return Object.prototype.hasOwnProperty.call(this.associations, alias); } @@ -3321,9 +3294,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. @@ -3332,66 +3305,78 @@ 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)) { + 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]; } /** @@ -3408,8 +3393,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} @@ -3419,12 +3404,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) { - options = _.defaults({ increment: false }, options, { - by: 1 + static async decrement(fields, options) { + return this.increment(fields, { + by: 1, + ...options, + increment: false }); - - return this.increment(fields, options); } static _optionsMustContainWhere(options) { @@ -3438,7 +3423,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) => { @@ -3447,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) { @@ -3494,11 +3479,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') { @@ -3583,9 +3568,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 * @@ -3609,12 +3594,12 @@ 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; } // If raw, .changed() shouldn't be true - this._previousDataValues = _.clone(this.dataValues); + this._previousDataValues = { ...this.dataValues }; } else { // Loop and call set if (options.attributes) { @@ -3641,7 +3626,7 @@ class Model { if (options.raw) { // If raw, .changed() shouldn't be true - this._previousDataValues = _.clone(this.dataValues); + this._previousDataValues = { ...this.dataValues }; } } return this; @@ -3707,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] && !_.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; @@ -3826,12 +3810,15 @@ 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. + * + * 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). * - * 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. + * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. * - * @param {Object} [options] save options + * 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. * @param {boolean} [options.validate=true] If false, validations won't be run. @@ -3843,7 +3830,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.'); } @@ -3881,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); } @@ -3918,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.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.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']) @@ -3980,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.runHooks(`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; } /** @@ -4112,35 +4082,33 @@ 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} */ - 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; } /** @@ -4148,14 +4116,14 @@ 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 * * @returns {Promise} */ - validate(options) { + async validate(options) { return new InstanceValidator(this, options).validate(); } @@ -4168,12 +4136,12 @@ 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} */ - 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); @@ -4196,13 +4164,13 @@ class Model { options.defaultFields = options.fields; } - return this.save(options); + return await this.save(options); } /** * 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 @@ -4210,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.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()); - } + force: false, + ...options + }; - 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(() => { - // Run after hook - if (options.hooks) { - return this.constructor.runHooks('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; } /** @@ -4271,38 +4239,37 @@ 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 * * @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.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 })); - }).tap(() => { - // Run after hook - if (options.hooks) { - return this.constructor.runHooks('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; } /** @@ -4324,8 +4291,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. @@ -4336,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; } /** @@ -4364,8 +4333,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. @@ -4375,12 +4344,12 @@ class Model { * * @returns {Promise} */ - decrement(fields, options) { - options = _.defaults({ increment: false }, options, { - by: 1 + async decrement(fields, options) { + return this.increment(fields, { + by: 1, + ...options, + increment: false }); - - return this.increment(fields, options); } /** @@ -4425,7 +4394,7 @@ class Model { * @see * {@link Model#get} * - * @returns {Object} + * @returns {object} */ toJSON() { return _.cloneDeep( @@ -4440,12 +4409,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. @@ -4461,16 +4430,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` @@ -4496,10 +4465,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' @@ -4517,10 +4486,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/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/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 f61b34f2832f..000000000000 --- a/lib/query-interface.js +++ /dev/null @@ -1,1473 +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) { - options = 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) { - options = 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) { - options = 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) { - options = 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) { - options = 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 = _.clone(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 = _.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 = []; - - // 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) { - 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; - - 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(); - } - - options = options || {}; - - 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(); - } - - 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 }) - )); - } - - /** - * 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, 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) { - options = 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)'); - } - - options = options || {}; - 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) { - 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); - 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 = {}; - options = options || {}; - - if (_.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) { - options = 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 })); - } - - - /** - * Returns all foreign key constraints of a table - * - * @param {string[]} tableNames table names - * @param {Object} [options] Query options - * - * @returns {Promise} - */ - 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) { - options = 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) { - 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); - 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 = _.clone(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 = _.clone(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 = _.clone(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 = _.clone(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 = 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 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 = 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!'); - } - - 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); - options = options || {}; - if (sql) { - return this.sequelize.query(sql, options); - } - return Promise.resolve(); - } - - dropTrigger(tableName, triggerName, options) { - const sql = this.QueryGenerator.dropTrigger(tableName, triggerName); - options = options || {}; - - if (sql) { - return this.sequelize.query(sql, options); - } - return Promise.resolve(); - } - - renameTrigger(tableName, oldTriggerName, newTriggerName, options) { - const sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName); - options = options || {}; - - 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); - options = 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); - options = options || {}; - - 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); - options = options || {}; - - 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 de50901d6fec..cbd79d081d47 --- 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,13 +11,11 @@ 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 Association = require('./associations/index'); const Validator = require('./utils/validator-extras').validator; @@ -126,49 +125,50 @@ 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. * @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 + * @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! * @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 + * @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. * @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. 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. + * @param {boolean} [options.logQueryParameters=false] A flag that defines if show bind parameters in log. */ constructor(database, username, password, options) { let config; @@ -211,10 +211,33 @@ 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 }) @@ -224,7 +247,7 @@ class Sequelize { Sequelize.runHooks('beforeInit', config, options); - this.options = Object.assign({ + this.options = { dialect: null, dialectModule: null, dialectModulePath: null, @@ -257,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'); @@ -321,16 +345,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` @@ -339,8 +363,6 @@ class Sequelize { this.modelManager = new ModelManager(this); this.connectionManager = this.dialect.connectionManager; - this.importCache = {}; - Sequelize.runHooks('afterInit', this); } @@ -377,7 +399,6 @@ class Sequelize { * @returns {QueryInterface} An instance (singleton) of QueryInterface. */ getQueryInterface() { - this.queryInterface = this.queryInterface || new QueryInterface(this); return this.queryInterface; } @@ -387,14 +408,13 @@ 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. - * @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 * @@ -456,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. * @@ -496,43 +484,39 @@ 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 - * @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} * * @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; @@ -576,98 +560,97 @@ 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; + let bindParameters; + + if (options.bind) { + [sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect); + } - 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; } + }; - 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; - } - }; + const retryOptions = { ...this.options.retry, ...options.retry }; - const retryOptions = Object.assign({}, this.options.retry, options.retry || {}); + return retry(async () => { + if (options.transaction === undefined && Sequelize._cls) { + options.transaction = Sequelize._cls.get('transaction'); + } - return Promise.resolve(retry(() => Promise.try(() => { - if (options.transaction === undefined && Sequelize._cls) { - options.transaction = Sequelize._cls.get('transaction'); - } + checkTransaction(); - checkTransaction(); + 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); - 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)); - }); + try { + await this.runHooks('beforeQuery', options, query); + checkTransaction(); + return await query.run(sql, bindParameters); + } finally { + await this.runHooks('afterQuery', options, query); + if (!options.transaction) { + await this.connectionManager.releaseConnection(connection); + } + } + }, retryOptions); } /** * 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 * * @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'); @@ -686,7 +669,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); } /** @@ -697,7 +680,7 @@ class Sequelize { * @returns {string} */ escape(value) { - return this.getQueryInterface().escape(value); + return this.dialect.queryGenerator.escape(value); } /** @@ -710,13 +693,13 @@ 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} */ - createSchema(schema, options) { - return this.getQueryInterface().createSchema(schema, options); + async createSchema(schema, options) { + return await this.getQueryInterface().createSchema(schema, options); } /** @@ -725,13 +708,13 @@ 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} */ - showAllSchemas(options) { - return this.getQueryInterface().showAllSchemas(options); + async showAllSchemas(options) { + return await this.getQueryInterface().showAllSchemas(options); } /** @@ -741,13 +724,13 @@ 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} */ - dropSchema(schema, options) { - return this.getQueryInterface().dropSchema(schema, options); + async dropSchema(schema, options) { + return await this.getQueryInterface().dropSchema(schema, options); } /** @@ -756,85 +739,86 @@ 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} */ - dropAllSchemas(options) { - return this.getQueryInterface().dropAllSchemas(options); + async dropAllSchemas(options) { + return await this.getQueryInterface().dropAllSchemas(options); } /** * 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} */ - sync(options) { - options = _.clone(options) || {}; - options.hooks = options.hooks === undefined ? true : !!options.hooks; - options = _.defaults(options, this.options.sync, this.options); + async sync(options) { + options = { + ...this.options, + ...this.options.sync, + ...options, + hooks: options ? options.hooks !== false : true + }; 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.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 } - }).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; } /** * 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} * * @see * {@link Model.truncate} for more information */ - truncate(options) { + async truncate(options) { const models = []; this.modelManager.forEachModel(model => { @@ -843,12 +827,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); } /** @@ -858,12 +841,12 @@ 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} */ - drop(options) { + async drop(options) { const models = []; this.modelManager.forEachModel(model => { @@ -872,28 +855,31 @@ class Sequelize { } }, { reverse: false }); - return Promise.each(models, model => model.drop(options)); + for (const model of models) await model.drop(options); } /** * 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} */ - 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); } /** @@ -985,7 +971,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 * @@ -1001,7 +987,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 * @@ -1017,7 +1003,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 * @@ -1038,9 +1024,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) { @@ -1053,25 +1039,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'); @@ -1081,7 +1070,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 @@ -1090,7 +1079,7 @@ class Sequelize { * * @returns {Promise} */ - transaction(options, autoCallback) { + async transaction(options, autoCallback) { if (typeof options === 'function') { autoCallback = options; options = undefined; @@ -1098,41 +1087,50 @@ 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 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); - }); + 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 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` - * 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 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; - - Promise.config({ - asyncHooks: true - }); + Sequelize._cls = ns; // return Sequelize for chaining return this; @@ -1282,29 +1280,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} */ @@ -1312,18 +1309,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; @@ -1337,6 +1337,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} */ @@ -1344,13 +1345,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..f8e32f18df92 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()`. @@ -15,7 +13,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 @@ -26,13 +24,14 @@ 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 = Object.assign({ + this.options = { type: sequelize.options.transactionType, isolationLevel: sequelize.options.isolationLevel, - readOnly: false - }, options || {}); + readOnly: false, + ...options + }; this.parent = this.options.transaction; @@ -52,28 +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}`); } - this._clearCls(); - - return this - .sequelize - .getQueryInterface() - .commitTransaction(this, this.options) - .finally(() => { - this.finished = 'commit'; - if (!this.parent) { - return this.cleanup(); - } - return null; - }).tap( - () => Promise.each( - this._afterCommitHooks, - hook => Promise.resolve(hook.apply(this, [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]); + } + } } /** @@ -81,30 +72,33 @@ 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 { + this.cleanup(); + } } - prepareEnvironment(useCLS) { + /** + * 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) { @@ -121,50 +115,57 @@ 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; - })); - }) - .tap(() => { - if (useCLS && this.sequelize.constructor._cls) { - this.sequelize.constructor._cls.set('transaction', this); - } - return null; - }); + 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() { + // 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; @@ -202,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 @@ -229,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 @@ -286,7 +289,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..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(_.values(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; } }); @@ -182,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,8 +270,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; } @@ -315,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) { @@ -380,8 +377,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 +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 {} @@ -488,8 +486,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 +498,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 +510,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 +522,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 +547,7 @@ exports.generateEnumName = generateEnumName; /** * Returns an new Object which keys are camelized * - * @param {Object} obj + * @param {object} obj * @returns {string} * @private */ @@ -570,9 +568,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 +600,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/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..ac9d62216c71 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/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-lock.json b/package-lock.json deleted file mode 100644 index 76d98402b3dd..000000000000 --- a/package-lock.json +++ /dev/null @@ -1,13651 +0,0 @@ -{ - "name": "sequelize", - "version": "6.0.0-beta.4", - "lockfileVersion": 1, - "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==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", - "dev": true, - "requires": { - "@babel/types": "^7.7.4", - "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.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", - "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": { - "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==", - "dev": true, - "requires": { - "@babel/types": "^7.7.4" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/types": "^7.7.4" - } - }, - "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "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.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", - "dev": true - }, - "@babel/runtime": { - "version": "7.7.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.6.tgz", - "integrity": "sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.2" - }, - "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==", - "dev": true - } - } - }, - "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" - } - }, - "@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==", - "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", - "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.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "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.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-8.2.0.tgz", - "integrity": "sha512-8fJ5pmytc38yw2QWbTTJmXLfSiWPwMkHH4govo9zJ/+ERPBF2jvlxD/dQvk24ezcizjKc6LFka2edYC4OQ+Dgw==", - "dev": true, - "requires": { - "@commitlint/format": "^8.2.0", - "@commitlint/lint": "^8.2.0", - "@commitlint/load": "^8.2.0", - "@commitlint/read": "^8.2.0", - "babel-polyfill": "6.26.0", - "chalk": "2.4.2", - "get-stdin": "7.0.0", - "lodash": "4.17.14", - "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==", - "dev": true, - "requires": { - "@commitlint/config-angular-type-enum": "^8.2.0" - } - }, - "@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==", - "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==", - "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 - } - } - }, - "@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==", - "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==", - "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==", - "dev": true, - "requires": { - "@types/semver": "^6.0.1", - "semver": "6.2.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==", - "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==", - "dev": true, - "requires": { - "@commitlint/is-ignored": "^8.2.0", - "@commitlint/parse": "^8.2.0", - "@commitlint/rules": "^8.2.0", - "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 - } - } - }, - "@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==", - "dev": true, - "requires": { - "@commitlint/execute-rule": "^8.2.0", - "@commitlint/resolve-extends": "^8.2.0", - "babel-runtime": "^6.23.0", - "chalk": "2.4.2", - "cosmiconfig": "^5.2.0", - "lodash": "4.17.14", - "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==", - "dev": true - }, - "@commitlint/parse": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-8.2.0.tgz", - "integrity": "sha512-vzouqroTXG6QXApkrps0gbeSYW6w5drpUk7QAeZIcaCSPsQXDM8eqqt98ZzlzLJHo5oPNXPX1AAVSTrssvHemA==", - "dev": true, - "requires": { - "conventional-changelog-angular": "^1.3.3", - "conventional-commits-parser": "^2.1.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==", - "dev": true, - "requires": { - "@commitlint/top-level": "^8.2.0", - "@marionebl/sander": "^0.6.0", - "babel-runtime": "^6.23.0", - "git-raw-commits": "^1.3.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==", - "dev": true, - "requires": { - "@types/node": "^12.0.2", - "import-fresh": "^3.0.0", - "lodash": "4.17.14", - "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==", - "dev": true, - "requires": { - "@commitlint/ensure": "^8.2.0", - "@commitlint/message": "^8.2.0", - "@commitlint/to-lines": "^8.2.0", - "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==", - "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==", - "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.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": "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 - } - } - }, - "@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" - } - }, - "@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/endpoint": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.5.1.tgz", - "integrity": "sha512-nBFhRUb5YzVTCX/iAK1MgQ4uWo89Gu0TH00qQHoYRCsE12dWcG1OiLd7v2EIo2+tpUKPMOQ62QFy9hy9Vg2ULg==", - "dev": true, - "requires": { - "@octokit/types": "^2.0.0", - "is-plain-object": "^3.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 - } - } - }, - "@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==", - "dev": true, - "requires": { - "@octokit/endpoint": "^5.5.0", - "@octokit/request-error": "^1.0.1", - "@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" - }, - "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 - } - } - }, - "@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==", - "dev": true, - "requires": { - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "@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==", - "dev": true, - "requires": { - "@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": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.0.2.tgz", - "integrity": "sha512-StASIL2lgT3TRjxv17z9pAqbnI7HGu9DrJlg3sEBFfCLaMEqp+O3IQPUF6EZtQ4xkAu2ml6kMBBCtGxjvmtmuQ==", - "dev": true, - "requires": { - "@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": "6.3.3", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-6.3.3.tgz", - "integrity": "sha512-Pyv1ZL2u5AIOY4YbxFCAB5J1PEh5yON8ylbfiPiriDGGW6Uu1U3Y8lysMtWu+FUD5x7tSnyIzhqx0+fxPxqbgw==", - "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" - }, - "dependencies": { - "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==", - "dev": true, - "requires": { - "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" - } - } - } - }, - "@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": "5.5.5", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-5.5.5.tgz", - "integrity": "sha512-Wo9OIULMRydbq+HpFh9yiLvra1XyEULPro9Tp4T5MQJ0WZyAQ3YQm74IdT8Pe/UmVDq2nfpT1oHrWkwOc4loHg==", - "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": "^2.1.0", - "https-proxy-agent": "^3.0.0", - "issue-parser": "^5.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": { - "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==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "globby": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", - "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", - "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" - } - }, - "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==", - "dev": true - } - } - }, - "@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==", - "dev": true, - "requires": { - "@semantic-release/error": "^2.2.0", - "aggregate-error": "^3.0.0", - "execa": "^3.2.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", - "tempy": "^0.3.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==", - "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" - } - }, - "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" - } - }, - "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" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.0.tgz", - "integrity": "sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ==", - "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 - }, - "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" - } - }, - "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", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "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" - } - } - } - }, - "@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==", - "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": { - "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==", - "dev": true, - "requires": { - "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" - } - }, - "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.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" - } - }, - "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.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": "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 - }, - "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" - } - }, - "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" - } - } - } - }, - "@sinonjs/commons": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", - "integrity": "sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@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==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@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 - }, - "@types/bluebird": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.29.tgz", - "integrity": "sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw==", - "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/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 - }, - "@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/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.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.17.tgz", - "integrity": "sha512-Is+l3mcHvs47sKy+afn2O1rV4ldZFU7W8101cNlOd+MRbjM4Onida8jSZnJdTe/0Pcf25g9BNIUsuugmE6puHA==" - }, - "@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/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/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", - "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": "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", - "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" - } - }, - "acorn-jsx": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", - "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", - "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.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", - "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==", - "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" - } - }, - "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.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "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-escapes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", - "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "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=" - }, - "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": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", - "dev": true, - "requires": { - "default-require-extensions": "^2.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 - }, - "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-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", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "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 - }, - "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", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "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==", - "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 - }, - "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=", - "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.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", - "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==", - "dev": true - }, - "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-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" - } - }, - "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 - }, - "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", - "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 - }, - "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 - }, - "bl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", - "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", - "dev": true, - "requires": { - "readable-stream": "^2.3.5", - "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", - "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": "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" - } - } - } - }, - "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 - }, - "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", - "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-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 - }, - "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": "3.0.2", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", - "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", - "dev": true, - "requires": { - "hasha": "^3.0.0", - "make-dir": "^2.0.0", - "package-hash": "^3.0.0", - "write-file-atomic": "^2.4.2" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, - "requires": { - "callsites": "^2.0.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - } - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "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.5.0", - "resolved": "https://registry.npmjs.org/chai-datetime/-/chai-datetime-1.5.0.tgz", - "integrity": "sha1-N0LxiwJMdbdqK37uKRZiMkRnWWw=", - "dev": true, - "requires": { - "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", - "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 - }, - "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.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" - } - }, - "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "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", - "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": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", - "dev": true, - "requires": { - "slice-ansi": "0.0.4", - "string-width": "^1.0.1" - }, - "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" - } - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "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" - } - } - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "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" - }, - "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" - } - } - } - }, - "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 - }, - "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", - "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" - } - }, - "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.5.5", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.5.5.tgz", - "integrity": "sha512-oB3TinFT+PV3p8UwDQt71+HkG03+zwPwikDlKU6ZDmql6QX2zFlQ+G0GGSDqyJhdZi4PSlzFBm+YJ+ebOX3Vgw==", - "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" - } - }, - "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", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "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.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.11.tgz", - "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" - }, - "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" - } - } - } - }, - "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": "2.1.7", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz", - "integrity": "sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ==", - "dev": true, - "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.0", - "lodash": "^4.2.1", - "meow": "^4.0.0", - "split2": "^2.0.0", - "through2": "^2.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==", - "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" - } - } - } - }, - "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" - } - }, - "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", - "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 - } - } - }, - "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", - "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==", - "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 - }, - "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": "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" - } - }, - "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-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", - "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 - } - } - }, - "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", - "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": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", - "dev": true, - "requires": { - "strip-bom": "^3.0.0" - } - }, - "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" - } - }, - "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" - } - } - } - }, - "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", - "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": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "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==" - }, - "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": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", - "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" - } - }, - "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": "4.5.1", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-4.5.1.tgz", - "integrity": "sha512-Xtmr+ordf8POu3NcNzx3eOa2zHyfD4h3fPHX5fLklkWa86ck35n1c9oZmyUnVPUl9zHnpZWdWtCUBPSWEagjCQ==", - "dev": true, - "requires": { - "execa": "^3.2.0", - "java-properties": "^1.0.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==", - "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" - } - }, - "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" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.0.tgz", - "integrity": "sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ==", - "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", - "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" - } - } - } - }, - "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==", - "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 - }, - "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", - "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.12.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", - "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", - "dev": true, - "optional": true, - "requires": { - "esprima": "^3.1.3", - "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", - "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" - } - }, - "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-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" - } - }, - "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.7.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.7.2.tgz", - "integrity": "sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng==", - "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 - }, - "globals": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", - "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "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" - } - } - } - }, - "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==", - "dev": true, - "requires": { - "comment-parser": "^0.5.4", - "jsdoctypeparser": "3.1.0", - "lodash": "^4.17.11" - } - }, - "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==", - "dev": true, - "requires": { - "ramda": "^0.26.1" - } - }, - "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.1.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", - "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", - "dev": true, - "requires": { - "acorn": "^7.1.0", - "acorn-jsx": "^5.1.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": { - "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.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "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 - }, - "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" - } - }, - "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", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "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", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "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=", - "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==", - "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" - }, - "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": { - "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 - }, - "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.6.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", - "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", - "dev": true, - "requires": { - "reusify": "^1.0.0" - } - }, - "figures": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", - "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "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" - }, - "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" - } - } - } - }, - "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==", - "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.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", - "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" - } - }, - "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", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "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=", - "dev": true, - "requires": { - "cross-spawn": "^4", - "signal-exit": "^3.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "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 - } - } - }, - "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" - } - }, - "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", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "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" - } - }, - "fs-jetpack": { - "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" - } - }, - "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" - } - }, - "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 - }, - "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 - }, - "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", - "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": { - "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" - } - } - } - }, - "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" - } - }, - "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-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 - }, - "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" - } - }, - "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", - "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" - } - } - } - }, - "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==", - "dev": true, - "requires": { - "dargs": "^4.0.1", - "lodash.template": "^4.0.2", - "meow": "^4.0.0", - "split2": "^2.0.0", - "through2": "^2.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==", - "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" - } - } - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", - "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": "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", - "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.5.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", - "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", - "dev": true, - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "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" - } - }, - "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" - } - }, - "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 - }, - "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": "3.0.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", - "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", - "dev": true, - "requires": { - "is-stream": "^1.0.1" - } - }, - "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.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", - "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", - "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.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "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" - }, - "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 - } - } - }, - "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": "3.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", - "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", - "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" - } - } - } - }, - "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": "1.3.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz", - "integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==", - "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" - }, - "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" - } - }, - "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": { - "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": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", - "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", - "dev": true, - "requires": { - "normalize-package-data": "^2.3.2", - "parse-json": "^4.0.0", - "pify": "^3.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.0.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.0.tgz", - "integrity": "sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^2.4.2", - "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", - "rxjs": "^6.4.0", - "string-width": "^4.1.0", - "strip-ansi": "^5.1.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==", - "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" - } - } - } - }, - "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" - } - }, - "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-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-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.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "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.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "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", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "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" - } - }, - "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-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": "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" - } - } - } - }, - "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-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-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", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "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==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "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.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-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-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", - "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 - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "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": "5.0.0", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-5.0.0.tgz", - "integrity": "sha512-q/16W7EPHRL0FKVz9NU++TUsoygXGj6JOi88oulyAcQG+IEZ0T6teVdE+VLbe19OfL/tbV8Wi3Dfo0HedeHW0Q==", - "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": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "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==", - "dev": true, - "requires": { - "append-transform": "^1.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==", - "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" - } - }, - "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==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "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==", - "dev": true, - "requires": { - "has-flag": "^3.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==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "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": "2.2.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", - "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", - "dev": true, - "requires": { - "handlebars": "^4.1.2" - } - }, - "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.4", - "resolved": "https://registry.npmjs.org/js-combinatorics/-/js-combinatorics-0.5.4.tgz", - "integrity": "sha512-PCqUIKGqv/Kjao1G4GE/Yni6QkCP2nWW3KnxL+8IGWPlP18vQpT8ufGMf4XUAAY8JHEryUCJbf51zG8329ntMg==", - "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" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdoctypeparser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-3.1.0.tgz", - "integrity": "sha512-JNbkKpDFqbYjg+IU3FNo7qjX7Opy7CwjHywT32zgAcz/d4lX6Umn5jOHVETUdnNNgGrMk0nEx1gvP0F4M0hzlQ==", - "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": { - "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-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-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 - }, - "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" - } - }, - "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.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", - "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.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "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" - } - }, - "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": "8.2.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.2.1.tgz", - "integrity": "sha512-n0tDGR/rTCgQNwXnUf/eWIpPNddGWxC32ANTNYsj2k02iZb7Cz5ox2tytwBu+2r0zDXMEMKw7Y9OD/qsav561A==", - "dev": true, - "requires": { - "chalk": "^2.3.1", - "commander": "^2.14.1", - "cosmiconfig": "^5.2.0", - "debug": "^3.1.0", - "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" - }, - "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" - } - } - } - }, - "listr": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", - "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" - }, - "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" - }, - "dependencies": { - "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=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.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 - } - } - }, - "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" - } - }, - "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==", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.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" - } - } - } - }, - "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.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "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.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", - "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.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 - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "log-update": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", - "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" - }, - "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 - }, - "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" - } - }, - "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==", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.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" - } - } - } - }, - "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", - "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" - } - }, - "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": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.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==", - "dev": true - } - } - }, - "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.1.4", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-2.1.4.tgz", - "integrity": "sha512-CMLbIKZCzxero94luo25IKpboEuUCvA2g2+NMUBdRYpBCDmnsiQ8kwdgqvuVQ13a4/ZKr87BC1nbe4b3UZXXmQ==", - "dev": true, - "requires": { - "@types/geojson": "^7946.0.7", - "@types/node": "^12.12.11", - "denque": "^1.4.1", - "iconv-lite": "^0.5.0", - "long": "^4.0.0", - "moment-timezone": "^0.5.27" - }, - "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==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "markdown-it": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-9.0.1.tgz", - "integrity": "sha512-XC9dMBHg28Xi7y5dPuLjM61upIGPJG8AiHNHYqIaXER2KNnn7eKnM5/sF0ImNnyoV224Ogn9b1Pck8VH4k0bxw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~1.1.1", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "markdownlint": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.16.0.tgz", - "integrity": "sha512-Zo+iPezP3eM6lLhKepkUw+X98H44lipIdx4d6faaugfB0+7VuDB3R0hXmx7z9F1N3/ypn46oOFgAD9iF++Ie6A==", - "dev": true, - "requires": { - "markdown-it": "9.0.1" - } - }, - "markdownlint-cli": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.18.0.tgz", - "integrity": "sha512-mQ2zvjMLoy0P2kb9Y03SqC24WPH4fTRN0/CyCorB122c4Chg9vWJKgUKBz3KR7swpzqmlI0SYq/7Blbqe4kb2g==", - "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", - "lodash.differencewith": "~4.5.0", - "lodash.flatten": "~4.4.0", - "markdownlint": "~0.16.0", - "minimatch": "~3.0.4", - "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 - } - } - }, - "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 - }, - "marked-terminal": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.3.0.tgz", - "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" - }, - "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 - } - } - }, - "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", - "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-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", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", - "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", - "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" - } - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "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==", - "dev": true - }, - "mime-types": { - "version": "2.1.25", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", - "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", - "dev": true, - "requires": { - "mime-db": "1.42.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 - }, - "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.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "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" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "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 - } - } - }, - "mocha": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz", - "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "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": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "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.0", - "yargs-parser": "13.1.1", - "yargs-unparser": "1.6.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 - }, - "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" - } - }, - "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" - } - }, - "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" - } - }, - "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.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 - }, - "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": "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" - } - }, - "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", - "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.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "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==", - "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": "1.7.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.7.0.tgz", - "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" - }, - "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==", - "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.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "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", - "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.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "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.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "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 - }, - "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", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "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==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^4.1.0", - "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.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==", - "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.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", - "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": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "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": { - "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": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", - "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.13.4", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.4.tgz", - "integrity": "sha512-vTcUL4SCg3AzwInWTbqg1OIaOXlzKSS8Mb8kc5avwrJpcvevDA5J9BhYSuei+fNs3pwOp4lzA5x2FVDXACvoXA==", - "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.6", - "bluebird": "^3.5.5", - "byte-size": "^5.0.1", - "cacache": "^12.0.3", - "call-limit": "^1.1.1", - "chownr": "^1.1.3", - "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.4", - "graceful-fs": "^4.2.3", - "has-unicode": "~2.0.1", - "hosted-git-info": "^2.8.5", - "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.0", - "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.1", - "move-concurrently": "^1.0.1", - "node-gyp": "^5.0.5", - "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.7", - "npm-pick-manifest": "^3.0.2", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.2", - "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.11", - "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.4.0", - "readdir-scoped-modules": "^1.1.0", - "request": "^2.88.0", - "retry": "^0.12.0", - "rimraf": "^2.6.3", - "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" - } - }, - "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, - "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.6", - "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.3", - "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", - "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.5.1", - "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": "1.0.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.2", - "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.4", - "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.5", - "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": "1.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.1.0", - "bundled": true, - "dev": true, - "requires": { - "ci-info": "^1.0.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.1.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": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "invert-kv": "^1.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.0", - "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" - } - }, - "meant": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "mem": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "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" - } - }, - "mimic-fn": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "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.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "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.0.5", - "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", - "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 - } - } - }, - "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.7", - "bundled": true, - "dev": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^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.2", - "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.2", - "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 - }, - "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": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.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" - } - }, - "p-finally": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "p-limit": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "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.11", - "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.7", - "bundled": true, - "dev": true, - "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true - } - } - }, - "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.4.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.3.2", - "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", - "bundled": true, - "dev": true - }, - "retry": { - "version": "0.12.0", - "bundled": true, - "dev": true - }, - "rimraf": { - "version": "2.6.3", - "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.3", - "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.2.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "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" - } - }, - "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.0", - "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", - "strip-ansi": "^3.0.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": "11.0.0", - "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": "^2.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", - "yargs-parser": "^9.0.2" - }, - "dependencies": { - "y18n": { - "version": "3.2.1", - "bundled": true, - "dev": true - } - } - }, - "yargs-parser": { - "version": "9.0.2", - "bundled": true, - "dev": true, - "requires": { - "camelcase": "^4.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-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-packlist": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz", - "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==", - "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-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-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", - "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": "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", - "js-yaml": "^3.13.1", - "make-dir": "^2.1.0", - "merge-source-map": "^1.1.0", - "resolve-from": "^4.0.0", - "rimraf": "^2.6.3", - "signal-exit": "^3.0.2", - "spawn-wrap": "^1.4.2", - "test-exclude": "^5.2.3", - "uuid": "^3.3.2", - "yargs": "^13.2.2", - "yargs-parser": "^13.0.0" - }, - "dependencies": { - "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.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 - }, - "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 - }, - "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "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-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", - "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-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", - "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.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" - } - }, - "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", - "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" - } - }, - "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 - } - } - }, - "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-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", - "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-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-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": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", - "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": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true - }, - "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-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": "3.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", - "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^3.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - } - }, - "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 - }, - "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": "*" - } - }, - "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", - "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-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 - }, - "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": "7.14.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.14.0.tgz", - "integrity": "sha512-TLsdOWKFu44vHdejml4Uoo8h0EwCjdIj9Z9kpz7pA5i8iQxOTwVb1+Fy+X86kW5AXKxQpYpYDs4j/qPDbro/lg==", - "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-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": "0.1.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", - "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=", - "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": "2.0.7", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.7.tgz", - "integrity": "sha512-UiJyO5B9zZpu32GSlP0tXy8J2NsJ9EFGFfz5v6PSbdz/1hBLX1rNiiy5+mAm5iJJYwfCv4A0EBcQLGWwjbpzZw==", - "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.1.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", - "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==", - "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 - }, - "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", - "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.2.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 - } - } - }, - "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" - } - }, - "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", - "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.4", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", - "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==", - "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 - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "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", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "psl": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", - "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==", - "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.26.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", - "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", - "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 - }, - "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 - } - } - }, - "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.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" - } - }, - "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 - }, - "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", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "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==", - "dev": true, - "requires": { - "rc": "^1.2.8", - "safe-buffer": "^5.0.1" - } - }, - "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" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "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", - "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.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" - }, - "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": { - "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": "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 - }, - "resolve": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", - "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", - "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" - } - }, - "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", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "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", - "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": "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-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "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", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", - "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==", - "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 - }, - "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", - "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": "15.13.31", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-15.13.31.tgz", - "integrity": "sha512-mrtYkH4p0FvXIRFCsr2r5il/A+Uj7oeeq+dgyojAbr4Tzywv9AlCYHeE3A8U3eE4bMJPiBV4YnQRsk1QS8yDDw==", - "dev": true, - "requires": { - "@semantic-release/commit-analyzer": "^6.1.0", - "@semantic-release/error": "^2.2.0", - "@semantic-release/github": "^5.1.0", - "@semantic-release/npm": "^5.0.5", - "@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", - "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.7.0", - "marked-terminal": "^3.2.0", - "p-locate": "^4.0.0", - "p-reduce": "^2.0.0", - "read-pkg-up": "^7.0.0", - "resolve-from": "^5.0.0", - "semver": "^6.0.0", - "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.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz", - "integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==", - "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 - }, - "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" - } - }, - "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.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" - } - }, - "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==", - "dev": true, - "requires": { - "lru-cache": "^5.1.1" - } - }, - "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" - } - }, - "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", - "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==", - "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==", - "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 - }, - "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 - }, - "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" - } - }, - "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 - }, - "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" - } - }, - "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" - } - }, - "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.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.0.2.tgz", - "integrity": "sha512-GH/X/hYt+x5hOat4LMnCqMd8r5Cv78heOMIJn1hr7QPPBqfeC6p89Y78+WB9yGDvfpCvgasfmWLzNzEioOUD9Q==", - "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": "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", - "decamelize": "^1.2.0" - } - } - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "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-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": "2.3.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", - "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==" - }, - "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 - }, - "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", - "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 - }, - "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.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "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" - } - } - } - }, - "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", - "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" - } - }, - "sinon-chai": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz", - "integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "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" - }, - "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": { - "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.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "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", - "integrity": "sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk=", - "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==", - "dev": true, - "requires": { - "foreground-child": "^1.5.6", - "mkdirp": "^0.5.0", - "os-homedir": "^1.0.1", - "rimraf": "^2.6.2", - "signal-exit": "^3.0.2", - "which": "^1.3.0" - } - }, - "spdx-correct": { - "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.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "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" - } - }, - "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", - "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", - "dev": true, - "requires": { - "through2": "^2.0.2" - } - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.1.tgz", - "integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==", - "dev": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.11.0", - "request": "^2.87.0" - } - }, - "sqlstring": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=", - "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" - } - }, - "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 - }, - "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", - "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", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, - "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 - }, - "string-argv": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", - "integrity": "sha1-2sMECGkMIfPDYwo/86BYd73L1zY=", - "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" - }, - "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" - } - } - } - }, - "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==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "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==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.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" - } - }, - "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-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", - "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": "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 - }, - "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": "1.0.1", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz", - "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", - "dev": true, - "requires": { - "has-flag": "^2.0.0", - "supports-color": "^5.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=", - "dev": true - } - } - }, - "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", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "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", - "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 - }, - "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" - } - } - } - }, - "taffydb": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.7.3.tgz", - "integrity": "sha1-KtNxaWKUmPylvIQkMJbTzeDsOjQ=", - "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" - } - }, - "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==", - "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", - "native-duplexpair": "^1.0.0", - "punycode": "^2.1.0", - "readable-stream": "^3.1.1", - "sprintf-js": "^1.1.2" - }, - "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==", - "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": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "dev": true - }, - "tempy": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz", - "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", - "dev": true, - "requires": { - "temp-dir": "^1.0.0", - "type-fest": "^0.3.1", - "unique-string": "^1.0.0" - }, - "dependencies": { - "type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", - "dev": true - } - } - }, - "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==", - "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" - } - } - } - }, - "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": "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" - } - }, - "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" - } - }, - "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-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-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=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "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" - } - }, - "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", - "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, - "optional": 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.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "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 - }, - "typescript": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", - "integrity": "sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==", - "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.7.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz", - "integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==", - "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 - } - } - }, - "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.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", - "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", - "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": "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" - } - }, - "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==", - "dev": true, - "requires": { - "os-name": "^3.1.0" - } - }, - "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 - }, - "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", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "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", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "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==" - }, - "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" - } - }, - "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" - } - }, - "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" - } - }, - "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": "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.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" - }, - "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": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz", - "integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==", - "dev": true, - "requires": { - "execa": "^1.0.0" - } - }, - "wkx": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", - "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==", - "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": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "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=", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "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 - }, - "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" - } - } - } - }, - "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": "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" - } - }, - "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 - }, - "xmldom": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", - "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", - "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.7.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz", - "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.6.3" - } - }, - "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", - "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.1" - }, - "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 - }, - "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" - } - }, - "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.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 - }, - "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" - } - }, - "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", - "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" - } - }, - "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 87e21d0ed52d..18cd5fce87a6 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,9 @@ { "name": "sequelize", "description": "Multi dialect ORM for Node.JS", - "version": "6.0.0-beta.4", + "version": "0.0.0-development", "maintainers": [ - "Sascha Depold ", - "Jan Aagaard Meier ", - "Daniel Durante ", - "Mick Hansen ", - "Sushant Dhiman " + "Pedro Augusto de Paula Barbosa " ], "repository": { "type": "git", @@ -25,65 +21,97 @@ "files": [ "lib", "types/index.d.ts", - "types/lib" + "types/lib", + "types/type-helpers" ], "license": "MIT", "dependencies": { - "bluebird": "^3.7.1", "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", + "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.0", - "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", + "chai-datetime": "^1.6.0", + "cheerio": "^1.0.0-rc.3", "cls-hooked": "^4.2.2", - "cross-env": "^5.2.1", - "env-cmd": "^8.0.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": "^4.1.1", - "eslint-plugin-mocha": "^5.2.1", - "fs-jetpack": "^2.2.2", - "husky": "^1.3.1", - "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": "^8.1.5", - "mariadb": "^2.1.1", - "markdownlint-cli": "^0.18.0", - "mocha": "^6.1.4", - "mysql2": "^1.6.5", - "nyc": "^14.1.1", - "pg": "^7.8.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.0.0", - "rimraf": "^2.6.3", - "semantic-release": "^15.13.16", - "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.0.6", - "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 84449e021577..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,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; }); }); @@ -1290,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 }); @@ -1345,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, @@ -1379,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: { @@ -1395,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) @@ -1484,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 }, @@ -1559,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 }, @@ -1613,37 +1540,35 @@ 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 }); - it('should catch EmptyResultError when rejectOnEmpty is set', function() { - const User = this.sequelize.define( - 'User', + 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', async function() { + const User = this.sequelize.define( + 'User', { username: DataTypes.STRING }, { rejectOnEmpty: true } ); @@ -1655,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', { @@ -1739,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() + ]); + + 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).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 }, @@ -1937,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 }, @@ -1954,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'); @@ -1976,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 }); @@ -2088,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; }); }); @@ -2123,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 @@ -2175,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, @@ -2197,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', () => { @@ -2219,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', () => { @@ -2313,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 }); - expect(project).to.be.ok; - return this.user.removeProject(project, { - logging: spy - }).return(project); - }).then(() => { - expect(spy).to.have.been.calledOnce; + const [user0, project0] = await Promise.all([ + this.User.create({ name: 'Matt' }), + this.Project.create({ name: 'Good Will Hunting' }) + ]); + + 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, { @@ -2412,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 [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 user = group.Users[0]; - expect(user).to.be.ok; + await user0.addProject(project0); + await group1.addUser(user0); + const group0 = group1; - const project = user.Projects[0]; - expect(project).to.be.ok; - expect(project.name).to.equal('Good Will Hunting'); + // 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'); }); }); @@ -2505,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 }); @@ -2521,141 +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); }); - 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 + } + }); - return this.sequelize.sync(); + const result = await this.users[0].getProjects({ through: { paranoid: false } }); + + expect(result.length).to.equal(3); + }); + + 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', () => { - 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 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 } }).return(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 } }).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 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 }); @@ -2663,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, @@ -2705,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 }); @@ -2793,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 }); @@ -2839,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 }); @@ -2869,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); }); }); }); @@ -2907,18 +2871,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); }); }); @@ -2931,42 +2894,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' }); @@ -2974,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() { @@ -3029,17 +2987,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]); }); }); }); @@ -3051,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); - }); - }); - - } + await this.sequelize.sync({ force: true }); - 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' }); - - 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) @@ -3226,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'); }); }); @@ -3263,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) }), @@ -3272,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) }), @@ -3312,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() { @@ -3381,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 69c5988b62b8..dc6a094039d3 100644 --- a/test/integration/associations/self.test.js +++ b/test/integration/associations/self.test.js @@ -3,13 +3,10 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../index'), - Promise = Sequelize.Promise, - _ = require('lodash'); + 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, @@ -18,41 +15,41 @@ 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' }); 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); @@ -61,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: { @@ -94,7 +88,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); @@ -104,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 index f0be74cbf8f2..746d43996842 100644 --- a/test/integration/cls.test.js +++ b/test/integration/cls.test.js @@ -4,9 +4,10 @@ const chai = require('chai'), expect = chai.expect, Support = require('./support'), Sequelize = Support.Sequelize, - Promise = Sequelize.Promise, cls = require('cls-hooked'), - current = Support.sequelize; + current = Support.sequelize, + delay = require('delay'), + sinon = require('sinon'); if (current.dialect.supports.transactions) { describe(Support.getTestDialectTeaser('CLS (Async hooks)'), () => { @@ -19,76 +20,64 @@ 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(); - }); + 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', 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() { - 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); }); }); - 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 Promise.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) { @@ -96,26 +85,23 @@ 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() { - return this.User.findOrCreate({ + it('does not leak outside findOrCreate', async function() { + await this.User.findOrCreate({ where: { name: 'Kafka' }, @@ -124,29 +110,28 @@ 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' }); - 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); }); }); }); @@ -155,11 +140,42 @@ 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); + } ); }); + + 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 d735c4394b7f..05214e9d8608 100644 --- a/test/integration/dialects/abstract/connection-manager.test.js +++ b/test/integration/dialects/abstract/connection-manager.test.js @@ -2,12 +2,12 @@ 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, - _ = require('lodash'); + Pool = require('sequelize-pool').Pool; const baseConf = Config[Support.getTestDialect()]; const poolEntry = { @@ -16,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(() => { @@ -33,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); @@ -44,36 +43,36 @@ 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); - 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; } - 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] } }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); const res = { queryType: 'read' @@ -81,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 = { @@ -92,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 = _.clone(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, - read: [_.clone(poolEntry)] + 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 = { @@ -133,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 6c9b10666f20..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().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 8497c73e852d..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.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 }); @@ -110,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(), @@ -125,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 }); @@ -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.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 }); @@ -245,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(), @@ -262,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(), @@ -290,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; + } }); }); }); @@ -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,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(), @@ -337,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 }); @@ -407,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(), @@ -422,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; + } }); }); }); @@ -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,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(), @@ -507,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 }); @@ -577,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(), @@ -592,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; }); }); }); @@ -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.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 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.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 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.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 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, @@ -913,25 +834,25 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { 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 c0700016db80..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,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(); @@ -43,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; @@ -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.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, @@ -229,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(); @@ -249,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) { @@ -271,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(); @@ -298,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, @@ -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.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, @@ -495,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(), @@ -509,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(), @@ -531,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 a6c96d3dc3f0..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,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(), @@ -36,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(), @@ -60,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(), @@ -83,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 }); @@ -102,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 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.beforeValidate(user => { @@ -131,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 => { @@ -146,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 => { @@ -161,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 => { @@ -176,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 => { @@ -196,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 0fb2a5f0e6b9..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'] + 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 275a24ab80bc..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,7 +31,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#define', () => { @@ -105,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(); @@ -271,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(); @@ -287,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(), @@ -308,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(), @@ -327,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() { @@ -343,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(() => { @@ -352,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(); @@ -368,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() { @@ -382,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!'); }); }); }); diff --git a/test/integration/include.test.js b/test/integration/include.test.js old mode 100755 new mode 100644 index 1556402f0177..7b049f9e2124 --- a/test/integration/include.test.js +++ b/test/integration/include.test.js @@ -2,13 +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(), - current = Support.sequelize; + current = Support.sequelize, + promiseProps = require('p-props'); const sortById = function(a, b) { return a.id < b.id ? -1 : 1; @@ -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,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', {}); @@ -264,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', {}); @@ -300,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', {}); @@ -332,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', {}); @@ -364,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', {}); @@ -407,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 @@ -445,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 @@ -543,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 }); @@ -627,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 @@ -659,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 }), @@ -738,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 }), @@ -782,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 }); @@ -820,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' @@ -872,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', {}); @@ -902,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 }), @@ -924,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 }), @@ -971,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 }); @@ -1024,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, @@ -1059,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' }, @@ -1075,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, @@ -1093,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, @@ -1112,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 29269bc7783a..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,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,40 @@ 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 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; }); }); }); @@ -174,260 +171,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('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 = 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,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 7fd178324ce9..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 .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 +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: { @@ -397,44 +386,40 @@ 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() { - 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(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() { - 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,17 +429,16 @@ 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() { - 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 @@ -473,22 +457,21 @@ 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() { - 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: { @@ -497,15 +480,14 @@ 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()}`, { + const Bar = this.sequelize.define(`Bar${Support.rand()}`, { field: { type: Sequelize.ENUM, values, @@ -517,16 +499,15 @@ 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()}`, { + const Bar = this.sequelize.define(`Bar${Support.rand()}`, { field: { type: Sequelize.ENUM, values, @@ -538,13 +519,13 @@ 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()}`, { + const Bar = this.sequelize.define(`Bar${Support.rand()}`, { field: { type: Sequelize.ENUM, values, @@ -556,10 +537,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 +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, @@ -594,94 +572,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 +679,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return Sequelize.Promise.all([ + await Promise.all([ expect(User.build({ password: 'short', salt: '42' @@ -715,7 +693,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 +707,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/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 cc8715f515d0..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 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 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 02a0ea623b29..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 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,8 +129,8 @@ 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 @@ -150,28 +140,27 @@ 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() { - 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 @@ -181,29 +170,28 @@ 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() { - 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: { @@ -218,25 +206,24 @@ 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() { - 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: { @@ -251,26 +238,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 +264,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,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 = 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,55 +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', 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 +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); @@ -479,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: { @@ -500,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 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 +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 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..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,55 +141,49 @@ 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() { - 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 @@ -210,28 +193,27 @@ 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() { - 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 @@ -241,29 +223,28 @@ 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() { - 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: { @@ -278,25 +259,24 @@ 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() { - 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: { @@ -311,153 +291,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() { - 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 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; }); }); 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 f38f54f3b67f..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 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 +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('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 +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,27 +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|\?)/); - } - }, - 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 +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' }]; + { 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); + 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); - users.forEach(user => { - if (user.secretValue === '42') { - expect(user.username).to.equal('Bill'); - } else { - expect(user.username).to.equal('Bob'); - } - }); - - }); - }); - }); + 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 +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, @@ -1122,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() { + 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, @@ -1457,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, @@ -1484,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 }); @@ -1641,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' }]; + { 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); - }); - }); + 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('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')); - }); - - 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 }, @@ -1832,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 }, @@ -1852,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; @@ -1940,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(); - }); - }); - }); - }); - }); - }); - }); - } - - 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); + 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 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'); + this.UserWithDec = this.sequelize.define('UserWithDec', { + value: Sequelize.DECIMAL(10, 3) }); - }); - }); - - 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 }); + 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 +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 }); @@ -2299,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' }); @@ -2418,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', { @@ -2534,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') { @@ -2549,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 }); @@ -2558,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') { @@ -2573,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 }); @@ -2581,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); @@ -2590,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/); @@ -2612,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 = { @@ -2629,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; }); }); @@ -2680,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'); }); }); } @@ -2705,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' }), @@ -2803,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, @@ -2918,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 }]; @@ -2938,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, @@ -2966,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 4255f4e86e19..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( - 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 @@ -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 82dc166a68d0..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,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() { @@ -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 874f812b1883..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,141 +389,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, @@ -559,54 +526,208 @@ 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', 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.'); }); @@ -615,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, @@ -651,99 +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)', 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: { @@ -763,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); }); }); @@ -863,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 e49b0c3d85ae..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,101 +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 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 60577fb9dd80..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,89 +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', 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 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, @@ -482,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, @@ -508,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( + (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' } }), - (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, @@ -616,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, @@ -637,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 }, { @@ -683,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, @@ -732,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', { }, { @@ -752,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, @@ -804,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; @@ -841,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) { @@ -852,75 +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', 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 @@ -932,121 +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(), - pad = function(number) { - if (number > 9) { - return number; - } - return `0${number}`; - }; - - expect(user.year).to.equal(`${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())}`); - }); - }); - }); + 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(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 } @@ -1054,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 } @@ -1073,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() { @@ -1117,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 @@ -1152,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 = [], @@ -1223,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 @@ -1311,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, @@ -1374,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, @@ -1439,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, @@ -1455,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 2b937d11c432..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 }); - it('should be able to handle binary values through associations as well...', function() { + await User.bulkCreate([ + { username: 'boo5', aBool: false }, + { username: 'boo6', aBool: true } + ]); + + 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: { @@ -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 }); + + await User.bulkCreate([ + { username: 'boo5', aBool: false }, + { username: 'boo6', aBool: true } + ]); - it('should be able to find a row between a certain date', function() { - return this.User.findAll({ + 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,163 +1252,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 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: Sequelize.VIRTUAL(Sequelize.BOOLEAN, ['createdAt']), @@ -1432,15 +1407,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); }); }); @@ -1467,7 +1442,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' @@ -1476,110 +1451,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 }); @@ -1593,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 5bfbb040e120..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 = 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 @@ -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 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..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 f6fbf7483564..c615efc78595 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,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 @@ -251,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, @@ -276,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, @@ -303,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, @@ -334,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, @@ -358,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, @@ -390,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 cdca083c3d4a..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,14 +36,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') { @@ -60,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; + } }); }); @@ -131,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; + } }); }); }); @@ -215,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(), @@ -880,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(); }); }); } @@ -955,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, @@ -979,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 }); @@ -1027,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 }); @@ -1071,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 @@ -1083,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, @@ -1096,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, @@ -1111,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 @@ -1139,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 @@ -1159,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 @@ -1173,7 +562,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { underscored: true }); - return this.sequelize.sync({ + await this.sequelize.sync({ alter: true }); }); @@ -1189,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; }); }); @@ -1217,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(); }); }); @@ -1263,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() { @@ -1280,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"]'); - }); + } }); }); }); @@ -1310,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); + } }); }); }); @@ -1331,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', { @@ -1573,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 54286d0b90db..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,99 +11,136 @@ if (!Support.sequelize.dialect.supports.deferrableConstraints) { describe(Support.getTestDialectTeaser('Sequelize'), () => { describe('Deferrable', () => { - beforeEach(function() { - this.run = function(deferrable, options) { - options = 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 f646600b5d53..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) => { @@ -13,15 +20,53 @@ before(function() { }); }); -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 ea140910788a..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,327 +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.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 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.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() { - 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 @@ -383,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]); }); }); @@ -486,81 +693,73 @@ 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', 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'); - }); - }); + 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 }); + 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; }); // 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; @@ -569,29 +768,32 @@ 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.join( - User.update({ username: 'joe' }, { - where: { - username: 'jan' - } - }).then(() => expect(transactionSpy).to.have.been.called ), // Update should not succeed before transaction has committed - Promise.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; + expect(transaction.finished).to.equal('commit'); + }), + + delay(4000) + .then(transactionSpy) + .then(() => transaction.commit()) + ]); }); } @@ -600,7 +802,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 @@ -608,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 @@ -799,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 83975b9621ac..57b351a8d984 100644 --- a/test/support.js +++ b/test/support.js @@ -2,17 +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')); @@ -24,29 +21,76 @@ 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) { @@ -69,18 +113,17 @@ const Support = { 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; }, @@ -90,41 +133,34 @@ 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 = {}; - 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() { @@ -171,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]; @@ -198,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 672187f800f1..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; }); }); @@ -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 diff --git a/test/unit/associations/has-many.test.js b/test/unit/associations/has-many.test.js index e17a52e2df9d..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'), @@ -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; }); }); @@ -144,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({}) @@ -160,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({ @@ -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', () => { 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 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/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', () => { }); }); }); - diff --git a/test/unit/dialects/mariadb/query-generator.test.js b/test/unit/dialects/mariadb/query-generator.test.js index f2c0c2c62151..b320c10d5d38 100644 --- a/test/unit/dialects/mariadb/query-generator.test.js +++ b/test/unit/dialects/mariadb/query-generator.test.js @@ -71,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\'' } ], @@ -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/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 17327f445137..e7b085d6db16 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' }, {}, {}], - 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\'' } ], @@ -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'], @@ -1280,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); @@ -1288,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 5e5d60ab466e..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; @@ -21,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); }); }); @@ -62,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; }); }); @@ -84,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; }); }); @@ -105,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; }); }); }); @@ -128,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 @@ -161,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(); @@ -173,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(); @@ -222,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); }); }); @@ -245,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: { @@ -265,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(), @@ -309,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(); @@ -343,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 Sequelize.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/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 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/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/instance/set.test.js b/test/unit/instance/set.test.js index 80d952c3a65b..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'); @@ -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', () => { + 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/destroy.test.js b/test/unit/model/destroy.test.js index 52728552da36..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(); }); @@ -35,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 index 1f435a5f37e3..087273a11a3d 100644 --- a/test/unit/model/find-or-create.test.js +++ b/test/unit/model/find-or-create.test.js @@ -34,24 +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(/abort/, () => { - 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' @@ -59,13 +58,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { transaction: { id: 123 } }; - return this.User.findOrCreate(options) - .then(() => { - expect.fail('expected to fail'); - }) - .catch(/abort/, () => { - 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 dac8db6e9afe..ed01ea620e04 100644 --- a/test/unit/model/findall.test.js +++ b/test/unit/model/findall.test.js @@ -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/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 0dad10210842..042b53faaa7e 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() { @@ -33,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 37c3263ee936..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,14 +180,14 @@ 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 @@ -198,15 +196,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})`; @@ -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 = UserSuccess.build({ name: succeedingValue }); - return expect(successfulUser.validate()).not.to.be.rejected; + 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([User.build({}), 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') @@ -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,11 +538,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(); } } }); @@ -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); }); @@ -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; }); }); @@ -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 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 old mode 100755 new mode 100644 index 2ad09d5e9500..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 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 3e9e186f0f84..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 @@ -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', () => { @@ -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 eeb1035541d7..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 @@ -444,12 +444,11 @@ 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];` }); }); - it('include (subQuery alias)', () => { + describe('include (subQuery alias)', () => { const User = Support.sequelize.define('User', { name: DataTypes.STRING, age: DataTypes.INTEGER @@ -466,29 +465,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];` }); }); @@ -749,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 d7351536778a..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: { @@ -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 78e59a9609fa..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 Sequelize.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 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 bdd9261b5418..f62f7f61573e 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -1,19 +1,20 @@ +import { ModelType } from '../index'; import { ValidationOptions } from './instance-validator'; import Model, { BulkCreateOptions, CountOptions, CreateOptions, - DestroyOptions, - RestoreOptions, - 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'; export type HookReturn = Promise | void; @@ -21,44 +22,59 @@ 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; + 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; + 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; @@ -67,33 +83,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 +156,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/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 cb54bb495229..4f760f7e8655 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -4,10 +4,9 @@ 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 { Transaction, LOCK } 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 Op = require('./operators'); @@ -36,7 +35,7 @@ export interface Transactionable { /** * Transaction to run query under */ - transaction?: Transaction; + transaction?: Transaction | null; } export interface SearchPathable { @@ -46,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 { @@ -104,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` @@ -118,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 @@ -139,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; @@ -150,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: @@ -200,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; @@ -222,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. @@ -305,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[]; } /** @@ -319,33 +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 - | Date // literal value - | Buffer // 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 | Buffer | 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 @@ -357,28 +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, nested?: 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. */ @@ -386,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` / @@ -402,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 @@ -452,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 @@ -460,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; @@ -506,7 +529,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 @@ -545,9 +570,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: ModelStatic } + | boolean; /** * Skip locked rows. Only supported in Postgres. */ @@ -561,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) @@ -569,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. */ @@ -579,7 +604,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 */ @@ -606,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`. @@ -615,7 +642,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,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)[]; + + /** + * dialect specific ON CONFLICT DO NOTHING / INSERT IGNORE + */ + ignoreDuplicates?: boolean; /** - * On Duplicate + * Return the affected rows (only for postgres) */ - onDuplicate?: string; + returning?: boolean | (keyof TAttributes)[]; /** * If false, validations won't be run. @@ -668,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 @@ -713,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 @@ -725,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. @@ -737,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 */ @@ -747,7 +781,7 @@ 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 @@ -757,13 +791,13 @@ export interface BulkCreateOptions extends Logging, Transactionable { /** * 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. @@ -772,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 @@ -803,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 @@ -814,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 @@ -835,16 +860,16 @@ 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. */ - 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 @@ -854,13 +879,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. * @@ -879,7 +897,7 @@ export interface UpdateOptions extends Logging, Transactionable, Paranoid { /** * Return the affected rows (only for postgres) */ - returning?: boolean; + returning?: boolean | (keyof TAttributes)[]; /** * How many rows to update (only for mysql and mariadb) @@ -895,7 +913,9 @@ export interface UpdateOptions extends Logging, Transactionable, Paranoid { /** * 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. @@ -913,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 * @@ -930,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 @@ -945,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 @@ -965,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. @@ -978,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; } /** @@ -991,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) @@ -1094,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 @@ -1134,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 @@ -1151,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; } @@ -1206,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); } /** @@ -1242,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 @@ -1294,9 +1313,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 @@ -1335,7 +1354,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 @@ -1351,13 +1370,13 @@ export interface ModelAttributeColumnOptions extends Co } /** - * 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; } /** @@ -1373,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. @@ -1427,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 @@ -1479,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 @@ -1512,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. */ @@ -1529,7 +1548,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; @@ -1541,7 +1584,7 @@ export abstract class Model extends Hooks { /** * 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 @@ -1606,8 +1649,12 @@ 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, M extends InstanceType>( + this: MS, + attributes: ModelAttributes, options: InitOptions + ): MS; /** * Remove attribute from model definition @@ -1620,7 +1667,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 @@ -1639,10 +1686,10 @@ 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; + ): ModelCtor; /** * Get the tablename of the model, taking schema into account. The method will return The name as a string @@ -1708,10 +1755,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 | readonly (string | ScopeOptions)[] | WhereAttributeHash + ): ModelCtor; /** * Add a new scope to the model @@ -1721,8 +1768,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: readonly any[]) => FindOptions, + options?: AddScopeOptions + ): void; /** * Search for multiple instances. @@ -1786,32 +1843,36 @@ 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, - 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 @@ -1822,24 +1883,30 @@ 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 @@ -1868,7 +1935,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 @@ -1877,43 +1944,47 @@ export abstract class Model extends Hooks { * 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 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; @@ -1921,28 +1992,30 @@ export abstract class Model extends Hooks { * 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]>; /** @@ -1957,8 +2030,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]>; /** @@ -1966,8 +2039,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]>; /** @@ -1985,21 +2058,15 @@ export abstract class Model extends Hooks { * 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. @@ -2013,66 +2080,104 @@ 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: 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. @@ -2082,7 +2187,7 @@ export abstract class Model extends Hooks { /** * Unscope the model */ - public static unscoped(this: M): M; + public static unscoped(this: M): M; /** * A hook that is run before validation @@ -2091,12 +2196,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; @@ -2107,12 +2212,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; @@ -2123,13 +2228,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; /** @@ -2139,13 +2244,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; /** @@ -2155,12 +2260,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; @@ -2171,12 +2276,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; @@ -2187,13 +2292,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; /** @@ -2203,13 +2308,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; /** @@ -2219,13 +2324,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; /** @@ -2235,13 +2340,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; /** @@ -2251,13 +2356,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; /** @@ -2267,13 +2372,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: readonly M[], options: BulkCreateOptions) => HookReturn ): void; public static afterBulkCreate( - this: { new (): M } & typeof Model, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn + this: ModelStatic, + fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn ): void; /** @@ -2282,8 +2387,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 @@ -2291,8 +2401,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 @@ -2300,8 +2416,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 @@ -2309,8 +2431,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 @@ -2318,8 +2446,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 @@ -2327,8 +2461,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 @@ -2336,8 +2476,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 @@ -2345,8 +2491,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 @@ -2355,13 +2507,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: readonly 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: readonly M[] | M | null, options: FindOptions) => HookReturn ): void; /** @@ -2402,7 +2554,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; /** @@ -2415,7 +2567,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; /** @@ -2472,7 +2624,7 @@ export abstract class Model extends Hooks { * @param options Options for the association */ public static hasMany( - this: ModelCtor, target: ModelCtor, options?: HasManyOptions + this: ModelStatic, target: ModelStatic, options?: HasManyOptions ): HasMany; /** @@ -2525,7 +2677,7 @@ export abstract class Model extends Hooks { * */ public static belongsToMany( - this: ModelCtor, target: ModelCtor, options: BelongsToManyOptions + this: ModelStatic, target: ModelStatic, options: BelongsToManyOptions ): BelongsToMany; /** @@ -2542,7 +2694,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` @@ -2552,12 +2704,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. @@ -2567,7 +2719,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; @@ -2595,10 +2747,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 @@ -2617,16 +2769,19 @@ export abstract class Model extends Hooks { /** * 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. + * + * 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. * - * 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. + * 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 @@ -2634,7 +2789,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. @@ -2649,8 +2804,13 @@ 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: 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 @@ -2683,9 +2843,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 | readonly K[] | Partial, + options?: IncrementDecrementOptionsWithBy ): Promise; /** @@ -2708,9 +2868,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 | readonly K[] | Partial, + options?: IncrementDecrementOptionsWithBy ): Promise; /** @@ -2719,15 +2879,15 @@ export abstract class Model extends Hooks { 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 @@ -2739,8 +2899,15 @@ export abstract class Model extends Hooks { 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 7f22da42e187..ec4529af6d42 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -1,9 +1,21 @@ 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 }; @@ -70,15 +82,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 { @@ -170,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 @@ -188,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. @@ -198,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 AddUniqueConstraintOptions extends BaseConstraintOptions { + type: 'unique'; + deferrable?: Deferrable; } -export interface AddDefaultConstraintOptions { +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 = @@ -248,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 { @@ -260,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. @@ -307,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; @@ -319,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 @@ -338,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 @@ -349,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 @@ -367,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; @@ -376,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 @@ -386,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 @@ -396,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 @@ -438,19 +465,19 @@ 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 */ - public getForeignKeyReferencesForTable(tableName: 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 @@ -460,7 +487,7 @@ export class QueryInterface { insertValues: object, updateValues: object, where: object, - model: typeof Model, + model: ModelType, options?: QueryOptions ): Promise; @@ -472,16 +499,16 @@ export class QueryInterface { records: object[], options?: QueryOptions, attributes?: string[] | string - ): Promise; + ): Promise; /** * Updates a row */ - public update( - instance: Model, + public update( + instance: M, tableName: TableName, values: object, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions ): Promise; @@ -491,7 +518,7 @@ export class QueryInterface { public bulkUpdate( tableName: TableName, values: object, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions, attributes?: string[] | string ): Promise; @@ -499,31 +526,36 @@ 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 + 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 */ - public increment( + public increment( instance: Model, tableName: TableName, values: object, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions ): Promise; @@ -534,7 +566,7 @@ export class QueryInterface { tableName: TableName, options: QueryOptionsWithWhere, attributeSelector: string | string[], - model?: typeof Model + model?: ModelType ): Promise; /** 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 9bce31458efe..a17d0fdb9021 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 { @@ -20,17 +19,14 @@ 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'; /** @@ -46,7 +42,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 +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 @@ -111,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 @@ -170,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)[]; @@ -331,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. @@ -357,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. @@ -371,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 */ @@ -380,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 @@ -506,8 +518,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 @@ -515,8 +527,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 @@ -542,8 +554,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 @@ -551,8 +563,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` @@ -560,8 +572,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` @@ -569,8 +584,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 @@ -578,8 +598,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 @@ -587,8 +610,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 @@ -596,8 +621,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 @@ -605,8 +630,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 @@ -614,8 +639,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 @@ -623,8 +648,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 @@ -632,8 +657,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 @@ -641,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 @@ -677,8 +702,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 @@ -686,8 +711,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 @@ -697,10 +722,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; /** @@ -711,10 +736,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; /** @@ -723,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 @@ -748,7 +773,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; /** @@ -775,7 +800,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 */ @@ -866,8 +891,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 @@ -875,8 +900,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 @@ -902,8 +927,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 @@ -911,8 +936,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 @@ -920,8 +945,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 @@ -929,8 +954,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 @@ -938,8 +963,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 @@ -947,8 +972,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 @@ -956,8 +981,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 @@ -965,8 +990,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 @@ -974,8 +999,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 @@ -983,8 +1008,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 @@ -992,8 +1017,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 @@ -1003,9 +1028,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 @@ -1013,8 +1038,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 @@ -1022,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 @@ -1080,7 +1105,7 @@ export class Sequelize extends Hooks { * Returns the database name. */ - public getDatabaseName() : string; + public getDatabaseName(): string; /** * Returns an instance of QueryInterface. @@ -1140,7 +1165,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 @@ -1156,44 +1185,19 @@ 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. * * 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 @@ -1206,21 +1210,14 @@ 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 + 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]>; /** @@ -1305,7 +1302,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 @@ -1327,12 +1324,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(); + * } * }) * ``` * @@ -1340,16 +1339,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 @@ -1433,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. @@ -1453,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. @@ -1473,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 73ac6f703ef6..0ebc2cad0933 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'; /** @@ -60,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 { @@ -151,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 20d0354ddbb4..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'; @@ -7,27 +8,19 @@ 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 ffae3593ee8b..8bc11bde189a 100644 --- a/types/test/hooks.ts +++ b/types/test/hooks.ts @@ -1,46 +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.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) => {}); + 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(); + }, + }; -/* - * covers types/lib/hooks.d.ts - */ + const sequelize = new Sequelize('uri', { hooks }); + TestModel.init({}, { sequelize, hooks }); -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.addHook('beforeSave', hooks.beforeSave!); + TestModel.addHook('afterSave', hooks.afterSave!); + TestModel.addHook('afterFind', hooks.afterFind!); + TestModel.addHook('beforeUpsert', hooks.beforeUpsert!); + TestModel.addHook('afterUpsert', hooks.afterUpsert!); -class TestModel extends Model { -} - -const hooks: Partial = { - beforeSave(t: TestModel, options: SaveOptions) { }, - afterSave(t: TestModel, options: SaveOptions) { }, - afterFind(t: TestModel | TestModel[] | null, options: FindOptions) { }, -}; + TestModel.beforeSave(hooks.beforeSave!); + TestModel.afterSave(hooks.afterSave!); + TestModel.afterFind(hooks.afterFind!); -TestModel.init({}, {sequelize, hooks }) + Sequelize.beforeSave(hooks.beforeSave!); + Sequelize.afterSave(hooks.afterSave!); + Sequelize.afterFind(hooks.afterFind!); + Sequelize.afterFind('namedAfterFind', hooks.afterFind!); +} -TestModel.addHook('beforeSave', (t: TestModel, options: SaveOptions) => { }); -TestModel.addHook('afterSave', (t: TestModel, options: SaveOptions) => { }); -TestModel.addHook('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => { }); +// #12959 +{ + const hooks: ModelHooks = 0 as any; -/* - * covers types/lib/model.d.ts - */ + 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>() }; +} -TestModel.beforeSave((t: TestModel, options: SaveOptions) => { }); -TestModel.afterSave((t: TestModel, options: SaveOptions) => { }); -TestModel.afterFind((t: TestModel | TestModel[] | null, options: FindOptions) => { }); +{ + 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/model.ts b/types/test/model.ts index 21e8e49cad33..0eb206404244 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -1,5 +1,8 @@ -import { Association, DataTypes, HasOne, Model, Sequelize } 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,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: [ @@ -28,6 +30,10 @@ MyModel.findOne({ ] }); +MyModel.findOne({ + include: [ { through: { paranoid: true } } ] +}); + MyModel.findOne({ include: [ { model: OtherModel, paranoid: true } @@ -40,17 +46,29 @@ 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 }); +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() { @@ -98,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; + +/** + * 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 99170ac61c22..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: { @@ -86,10 +106,15 @@ 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; }); +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: { 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 6a20cc592333..09c403b23810 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -1,75 +1,88 @@ -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'; 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: '' }, {}); -queryInterface.dropTable('nameOfTheExistingTable'); + await queryInterface.dropTable('nameOfTheExistingTable'); + await queryInterface.dropTable({ schema: '', tableName: 'nameOfTheExistingTable' }); -queryInterface.bulkDelete({ tableName: 'foo', schema: 'bar' }, {}, {}); + await 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' }, {}, {}); + 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'); + await queryInterface.renameTable( + { schema: '', tableName: 'Person' }, + { schema: '', tableName: 'User' }, + ); -queryInterface.showAllTables().then(tableNames => { - // do nothing -}); + const tableNames: string[] = await queryInterface.showAllTables(); -queryInterface.describeTable('Person').then(attributes => { /* attributes will be something like: @@ -86,89 +99,127 @@ 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.removeIndex('Person', 'SuperDuperIndex'); + // or -// or - -queryInterface.removeIndex('Person', ['firstname', 'lastname']); - -queryInterface.sequelize.transaction(trx => queryInterface.addConstraint('Person', ['firstname', 'lastname'], { - name: 'firstnamexlastname', - type: 'unique', - transaction: trx, -})) + 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.removeConstraint('Person', 'firstnamexlastname'); + await queryInterface.delete(null, 'Person', { + where: { + a: 1, + }, + }); -queryInterface.select(null, 'Person', { - where: { - a: 1, - }, -}); + class TestModel extends Model {} -queryInterface.delete(null, 'Person', { - where: { - a: 1, - }, -}); + await queryInterface.upsert("test", {"a": 1}, {"b": 2}, {"c": 3}, TestModel, {}); -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 516e39bc5c5a..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,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 e4aeaacf9fdc..b77cf12e4eaf 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, @@ -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 }) + ); +} 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 ad8a204e5598..037acbbe0b0b 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,112 +18,97 @@ where = { buffers: [Buffer.alloc(0)], null: null, date: new Date() -}; - -// 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 - -MyModel.findOne({ where: or }); -MyModel.findOne({ where: and }); - -where = Sequelize.and(); - -where = Sequelize.or(); - -where = { [Op.and]: [] }; +}).toMatchTypeOf(); -where = { - rank: Sequelize.and({ [Op.lt]: 1000 }, { [Op.eq]: null }), -}; +// Optional values +expectTypeOf<{ needed: number; optional?: number }>().toMatchTypeOf(); -where = { - rank: Sequelize.or({ [Op.lt]: 1000 }, { [Op.eq]: null }), -}; +// 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 = { - 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'] @@ -136,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==
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: